diff --git a/0016-parser_bison-Fix-for-broken-compatibility-with-older.patch b/0016-parser_bison-Fix-for-broken-compatibility-with-older.patch new file mode 100644 index 0000000..8283a48 --- /dev/null +++ b/0016-parser_bison-Fix-for-broken-compatibility-with-older.patch @@ -0,0 +1,83 @@ +From c13051bb51a4fcc1322faed773ee1dd6f81ed5f5 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:18 +0200 +Subject: [PATCH] parser_bison: Fix for broken compatibility with older dumps + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 22fab8681a50014174cdd02ace90f74b9e9eefe9 + +commit 22fab8681a50014174cdd02ace90f74b9e9eefe9 +Author: Phil Sutter +Date: Thu Oct 19 18:40:04 2023 +0200 + + parser_bison: Fix for broken compatibility with older dumps + + Commit e6d1d0d611958 ("src: add set element multi-statement + support") changed the order of expressions and other state attached to set + elements are expected in input. This broke parsing of ruleset dumps + created by nft commands prior to that commit. + + Restore compatibility by also accepting the old ordering. + + Fixes: e6d1d0d611958 ("src: add set element multi-statement support") + Signed-off-by: Phil Sutter + +Signed-off-by: Phil Sutter +--- + src/parser_bison.y | 6 ++++ + tests/shell/testcases/sets/elem_opts_compat_0 | 29 +++++++++++++++++++ + 2 files changed, 35 insertions(+) + create mode 100755 tests/shell/testcases/sets/elem_opts_compat_0 + +diff --git a/src/parser_bison.y b/src/parser_bison.y +index 5ced6e1..8eff50c 100644 +--- a/src/parser_bison.y ++++ b/src/parser_bison.y +@@ -4532,6 +4532,12 @@ meter_key_expr_alloc : concat_expr + + set_elem_expr : set_elem_expr_alloc + | set_elem_expr_alloc set_elem_expr_options ++ | set_elem_expr_alloc set_elem_expr_options set_elem_stmt_list ++ { ++ $$ = $1; ++ list_splice_tail($3, &$$->stmt_list); ++ xfree($3); ++ } + ; + + set_elem_key_expr : set_lhs_expr { $$ = $1; } +diff --git a/tests/shell/testcases/sets/elem_opts_compat_0 b/tests/shell/testcases/sets/elem_opts_compat_0 +new file mode 100755 +index 0000000..e012953 +--- /dev/null ++++ b/tests/shell/testcases/sets/elem_opts_compat_0 +@@ -0,0 +1,29 @@ ++#!/bin/sh ++ ++# ordering of element options and expressions has changed, make sure parser ++# accepts both ways ++ ++set -e ++ ++$NFT -f - < +Date: Fri, 17 Jul 2026 11:08:18 +0200 +Subject: [PATCH] tests/shell: add missing "elem_opts_compat_0.nodump" file + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit f65b2d12236174d477c55e96c4027cd51185ba5e + +commit f65b2d12236174d477c55e96c4027cd51185ba5e +Author: Thomas Haller +Date: Mon Oct 23 14:40:25 2023 +0200 + + tests/shell: add missing "elem_opts_compat_0.nodump" file + + This is an inconsistency. The test should have either a .nft or a + .nodump file. "./tools/check-tree.sh" enforces that and will in the + future run by `make check`. + + Fixes: 22fab8681a50 ("parser_bison: Fix for broken compatibility with older dumps") + Signed-off-by: Thomas Haller + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + tests/shell/testcases/sets/dumps/elem_opts_compat_0.nodump | 0 + 1 file changed, 0 insertions(+), 0 deletions(-) + create mode 100644 tests/shell/testcases/sets/dumps/elem_opts_compat_0.nodump + +diff --git a/tests/shell/testcases/sets/dumps/elem_opts_compat_0.nodump b/tests/shell/testcases/sets/dumps/elem_opts_compat_0.nodump +new file mode 100644 +index 0000000..e69de29 diff --git a/0018-tests-shell-cover-long-interface-name-in-0042chain_v.patch b/0018-tests-shell-cover-long-interface-name-in-0042chain_v.patch new file mode 100644 index 0000000..8d94727 --- /dev/null +++ b/0018-tests-shell-cover-long-interface-name-in-0042chain_v.patch @@ -0,0 +1,126 @@ +From 7d88deb4054d71dcd038ea003484be65226d8f15 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:18 +0200 +Subject: [PATCH] tests/shell: cover long interface name in + "0042chain_variable_0" test + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit f773041121d6d0d112fa9cb003fd791eacd6e43d + +commit f773041121d6d0d112fa9cb003fd791eacd6e43d +Author: Thomas Haller +Date: Mon Oct 23 19:00:46 2023 +0200 + + tests/shell: cover long interface name in "0042chain_variable_0" test + + IFNAMSIZ is 16. Adjust "0042chain_variable_0" to use an interface name + with the maximum allowed bytes length. + + Instead of adding an entirely different test, adjust an existing one to + use another interface name. The aspect for testing for a long interface + name is not special enough, to warrant a separate test. We can cover it + by extending an existing test. + + Note that the length check in "parser_bison.y" is wrong. The test checks + still for the wrong behavior and that "d23456789012345x" is accepted. + + Signed-off-by: Thomas Haller + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + .../testcases/chains/0042chain_variable_0 | 36 ++++++++++++++++--- + .../chains/dumps/0042chain_variable_0.nft | 4 +-- + 2 files changed, 34 insertions(+), 6 deletions(-) + +diff --git a/tests/shell/testcases/chains/0042chain_variable_0 b/tests/shell/testcases/chains/0042chain_variable_0 +index 1ea44e8..739dc05 100755 +--- a/tests/shell/testcases/chains/0042chain_variable_0 ++++ b/tests/shell/testcases/chains/0042chain_variable_0 +@@ -2,7 +2,8 @@ + + set -e + +-ip link add name dummy0 type dummy ++ip link add name d23456789012345 type dummy ++ + + EXPECTED="define if_main = \"lo\" + +@@ -14,22 +15,50 @@ table netdev filter1 { + + $NFT -f - <<< $EXPECTED + ++ ++EXPECTED="define if_main = \"lo\" ++ ++table netdev filter2 { ++ chain Main_Ingress2 { ++ type filter hook ingress devices = { \$if_main, d23456789012345x } priority -500; policy accept; ++ } ++}" ++ ++rc=0 ++$NFT -f - <<< $EXPECTED || rc=$? ++test "$rc" = 0 ++cat < +Date: Fri, 17 Jul 2026 11:08:18 +0200 +Subject: [PATCH] parser_bison: fix length check for ifname in + ifname_expr_alloc() + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 122dce6b35205a3df419a5cae9acfd6e83e8725a + +commit 122dce6b35205a3df419a5cae9acfd6e83e8725a +Author: Thomas Haller +Date: Mon Oct 23 19:00:47 2023 +0200 + + parser_bison: fix length check for ifname in ifname_expr_alloc() + + IFNAMSIZ is 16, and the allowed byte length of the name is one less than + that. Fix the length check and adjust a test for covering the longest + allowed interface name. + + This is obviously a change in behavior, because previously interface + names with length 16 were accepted and were silently truncated along the + way. Now they are rejected as invalid. + + Fixes: fa52bc225806 ("parser: reject zero-length interface names") + Signed-off-by: Thomas Haller + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/parser_bison.y | 3 ++- + tests/shell/testcases/chains/0042chain_variable_0 | 7 +------ + 2 files changed, 3 insertions(+), 7 deletions(-) + +diff --git a/src/parser_bison.y b/src/parser_bison.y +index 8eff50c..5c2d14b 100644 +--- a/src/parser_bison.y ++++ b/src/parser_bison.y +@@ -16,6 +16,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -158,7 +159,7 @@ static struct expr *ifname_expr_alloc(const struct location *location, + return NULL; + } + +- if (length > 16) { ++ if (length >= IFNAMSIZ) { + xfree(name); + erec_queue(error(location, "interface name too long"), queue); + return NULL; +diff --git a/tests/shell/testcases/chains/0042chain_variable_0 b/tests/shell/testcases/chains/0042chain_variable_0 +index 739dc05..a4b929f 100755 +--- a/tests/shell/testcases/chains/0042chain_variable_0 ++++ b/tests/shell/testcases/chains/0042chain_variable_0 +@@ -26,18 +26,13 @@ table netdev filter2 { + + rc=0 + $NFT -f - <<< $EXPECTED || rc=$? +-test "$rc" = 0 ++test "$rc" = 1 + cat < +Date: Fri, 17 Jul 2026 11:08:18 +0200 +Subject: [PATCH] tests/shell: fix mount command in "test-wrapper.sh" + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 65d94af52f11a2c5a44d5ae8aa3891ccb648c666 + +commit 65d94af52f11a2c5a44d5ae8aa3891ccb648c666 +Author: Thomas Haller +Date: Thu Nov 2 09:15:41 2023 +0100 + + tests/shell: fix mount command in "test-wrapper.sh" + + With Fedora 39 (util-linux-core-2.39.2-1.fc39), the mount command starts + to fail. It was still working with Fedora 38 (util-linux-core-2.38.1-4.fc38). + + $ unshare -f -p -m --mount-proc -U --map-root-user -n bash -c 'mount -t tmpfs --make-private /var/run && mount' + mount: /run: mount failed: Invalid argument. + + Not sure why this starts to fail. But arguably the command line + arguments were wrong. Fix it, we need a pseudo name for the device. + + Fixes: df6f1a3e0803 ("tests/shell: bind mount private /var/run/netns in test container") + Signed-off-by: Thomas Haller + Signed-off-by: Florian Westphal + +Signed-off-by: Phil Sutter +--- + tests/shell/helpers/test-wrapper.sh | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/tests/shell/helpers/test-wrapper.sh b/tests/shell/helpers/test-wrapper.sh +index 13b918f..898dcce 100755 +--- a/tests/shell/helpers/test-wrapper.sh ++++ b/tests/shell/helpers/test-wrapper.sh +@@ -48,7 +48,7 @@ if [ "$NFT_TEST_HAS_UNSHARED_MOUNT" = y ] ; then + # Note that this also hides everything that might reside in /var/run. + # That is desirable, as tests should not depend on content there (or if + # they do, we need to explicitly handle it as appropriate). +- if mount -t tmpfs --make-private "/var/run" ; then ++ if mount -t tmpfs --make-private tmpfs "/var/run" ; then + CLEANUP_UMOUNT_VAR_RUN=y + fi + mkdir -p /var/run/netns diff --git a/0021-tproxy-Drop-artificial-port-printing-restriction.patch b/0021-tproxy-Drop-artificial-port-printing-restriction.patch new file mode 100644 index 0000000..976c9a7 --- /dev/null +++ b/0021-tproxy-Drop-artificial-port-printing-restriction.patch @@ -0,0 +1,119 @@ +From 2927e76b0fb5b64e1a57467151fb76c3d513fa3a Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:18 +0200 +Subject: [PATCH] tproxy: Drop artificial port printing restriction + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit e4c9f9f7e0d1f83be18f6c4a418da503e9021b24 + +commit e4c9f9f7e0d1f83be18f6c4a418da503e9021b24 +Author: Phil Sutter +Date: Thu Nov 2 14:48:10 2023 +0100 + + tproxy: Drop artificial port printing restriction + + It does not make much sense to omit printing the port expression if it's + not a value expression: On one hand, input allows for more advanced + uses. On the other, if it is in-kernel, best nft can do is to try and + print it no matter what. Just ignoring ruleset elements can't be + correct. + + Fixes: 2be1d52644cf7 ("src: Add tproxy support") + Closes: https://bugzilla.netfilter.org/show_bug.cgi?id=1721 + Signed-off-by: Phil Sutter + Reviewed-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/statement.c | 2 +- + tests/py/inet/tproxy.t | 2 ++ + tests/py/inet/tproxy.t.json | 35 ++++++++++++++++++++++++++++++++++ + tests/py/inet/tproxy.t.payload | 12 ++++++++++++ + 4 files changed, 50 insertions(+), 1 deletion(-) + +diff --git a/src/statement.c b/src/statement.c +index 4756116..f5176e6 100644 +--- a/src/statement.c ++++ b/src/statement.c +@@ -989,7 +989,7 @@ static void tproxy_stmt_print(const struct stmt *stmt, struct output_ctx *octx) + expr_print(stmt->tproxy.addr, octx); + } + } +- if (stmt->tproxy.port && stmt->tproxy.port->etype == EXPR_VALUE) { ++ if (stmt->tproxy.port) { + if (!stmt->tproxy.addr) + nft_print(octx, " "); + nft_print(octx, ":"); +diff --git a/tests/py/inet/tproxy.t b/tests/py/inet/tproxy.t +index d23bbcb..9901df7 100644 +--- a/tests/py/inet/tproxy.t ++++ b/tests/py/inet/tproxy.t +@@ -19,3 +19,5 @@ meta l4proto 17 tproxy ip to :50080;ok + meta l4proto 17 tproxy ip6 to :50080;ok + meta l4proto 17 tproxy to :50080;ok + ip daddr 0.0.0.0/0 meta l4proto 6 tproxy ip to :2000;ok ++ ++meta l4proto 6 tproxy ip to 127.0.0.1:symhash mod 2 map { 0 : 23, 1 : 42 };ok +diff --git a/tests/py/inet/tproxy.t.json b/tests/py/inet/tproxy.t.json +index 7b3b11c..71b6fd2 100644 +--- a/tests/py/inet/tproxy.t.json ++++ b/tests/py/inet/tproxy.t.json +@@ -183,3 +183,38 @@ + } + } + ] ++ ++# meta l4proto 6 tproxy ip to 127.0.0.1:symhash mod 2 map { 0 : 23, 1 : 42 } ++[ ++ { ++ "match": { ++ "left": { ++ "meta": { ++ "key": "l4proto" ++ } ++ }, ++ "op": "==", ++ "right": 6 ++ } ++ }, ++ { ++ "tproxy": { ++ "addr": "127.0.0.1", ++ "family": "ip", ++ "port": { ++ "map": { ++ "data": { ++ "set": [ ++ [ 0, 23 ], ++ [ 1, 42 ] ++ ] ++ }, ++ "key": { ++ "symhash": { "mod": 2 } ++ } ++ } ++ } ++ } ++ } ++] ++ +diff --git a/tests/py/inet/tproxy.t.payload b/tests/py/inet/tproxy.t.payload +index 24bf8f6..2f41904 100644 +--- a/tests/py/inet/tproxy.t.payload ++++ b/tests/py/inet/tproxy.t.payload +@@ -61,3 +61,15 @@ inet x y + [ immediate reg 1 0x0000d007 ] + [ tproxy ip port reg 1 ] + ++# meta l4proto 6 tproxy ip to 127.0.0.1:symhash mod 2 map { 0 : 23, 1 : 42 } ++__map%d x b size 2 ++__map%d x 0 ++ element 00000000 : 00001700 0 [end] element 00000001 : 00002a00 0 [end] ++inet x y ++ [ meta load l4proto => reg 1 ] ++ [ cmp eq reg 1 0x00000006 ] ++ [ immediate reg 1 0x0100007f ] ++ [ hash reg 2 = symhash() % mod 2 ] ++ [ lookup reg 2 set __map%d dreg 2 ] ++ [ tproxy ip addr reg 1 port reg 2 ] ++ diff --git a/0022-meta-fix-hour-decoding-when-timezone-offset-is-negat.patch b/0022-meta-fix-hour-decoding-when-timezone-offset-is-negat.patch new file mode 100644 index 0000000..0ed2d95 --- /dev/null +++ b/0022-meta-fix-hour-decoding-when-timezone-offset-is-negat.patch @@ -0,0 +1,126 @@ +From 2db221f432962517f777f225aadf4db1cf18e1e4 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:18 +0200 +Subject: [PATCH] meta: fix hour decoding when timezone offset is negative + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit d392ddf243dcbf8a34726c777d2c669b1e8bfa85 + +commit d392ddf243dcbf8a34726c777d2c669b1e8bfa85 +Author: Florian Westphal +Date: Thu Nov 2 15:34:13 2023 +0100 + + meta: fix hour decoding when timezone offset is negative + + Brian Davidson says: + + meta hour rules don't display properly after being created when the + hour is on or after 00:00 UTC. The netlink debug looks correct for + seconds past midnight UTC, but displaying the rules looks like an + overflow or a byte order problem. I am in UTC-0400, so today, 20:00 + and later exhibits the problem, while 19:00 and earlier hours are + fine. + + meta.c only ever worked when the delta to UTC is positive. + We need to add in case the second counter turns negative after + offset adjustment. + + Also add a test case for this. + + Fixes: f8f32deda31d ("meta: Introduce new conditions 'time', 'day' and 'hour'") + Reported-by: Brian Davidson + Signed-off-by: Florian Westphal + +Signed-off-by: Phil Sutter +--- + src/meta.c | 11 +++- + .../testcases/listing/dumps/meta_time.nodump | 0 + tests/shell/testcases/listing/meta_time | 52 +++++++++++++++++++ + 3 files changed, 61 insertions(+), 2 deletions(-) + create mode 100644 tests/shell/testcases/listing/dumps/meta_time.nodump + create mode 100755 tests/shell/testcases/listing/meta_time + +diff --git a/src/meta.c b/src/meta.c +index b69dca2..1f1e33c 100644 +--- a/src/meta.c ++++ b/src/meta.c +@@ -495,9 +495,16 @@ static void hour_type_print(const struct expr *expr, struct output_ctx *octx) + + /* Obtain current tm, so that we can add tm_gmtoff */ + ts = time(NULL); +- if (ts != ((time_t) -1) && localtime_r(&ts, &cur_tm)) +- seconds = (seconds + cur_tm.tm_gmtoff) % SECONDS_PER_DAY; ++ if (ts != ((time_t) -1) && localtime_r(&ts, &cur_tm)) { ++ int32_t adj = seconds + cur_tm.tm_gmtoff; + ++ if (adj < 0) ++ adj += SECONDS_PER_DAY; ++ else if (adj >= SECONDS_PER_DAY) ++ adj -= SECONDS_PER_DAY; ++ ++ seconds = adj; ++ } + minutes = seconds / 60; + seconds %= 60; + hours = minutes / 60; +diff --git a/tests/shell/testcases/listing/dumps/meta_time.nodump b/tests/shell/testcases/listing/dumps/meta_time.nodump +new file mode 100644 +index 0000000..e69de29 +diff --git a/tests/shell/testcases/listing/meta_time b/tests/shell/testcases/listing/meta_time +new file mode 100755 +index 0000000..a976199 +--- /dev/null ++++ b/tests/shell/testcases/listing/meta_time +@@ -0,0 +1,52 @@ ++#!/bin/bash ++ ++set -e ++ ++TMP1=$(mktemp) ++TMP2=$(mktemp) ++ ++cleanup() ++{ ++ rm -f "$TMP1" ++ rm -f "$TMP2" ++} ++ ++check_decode() ++{ ++ TZ=$1 $NFT list chain t c | grep meta > "$TMP2" ++ diff -u "$TMP1" "$TMP2" ++} ++ ++trap cleanup EXIT ++ ++$NFT -f - <> "$TMP1" ++done ++ ++check_decode UTC ++ ++printf "\t\tmeta hour \"%02d:%02d\"-\"%02d:%02d\"\n" 23 0 23 59 > "$TMP1" ++for i in $(seq 0 22); do ++ printf "\t\tmeta hour \"%02d:%02d\"-\"%02d:%02d\"\n" $i 0 $i 59 >> "$TMP1" ++done ++check_decode UTC+1 ++ ++printf "\t\tmeta hour \"%02d:%02d\"-\"%02d:%02d\"\n" 1 0 1 59 > "$TMP1" ++for i in $(seq 2 23); do ++ printf "\t\tmeta hour \"%02d:%02d\"-\"%02d:%02d\"\n" $i 0 $i 59 >> "$TMP1" ++done ++printf "\t\tmeta hour \"%02d:%02d\"-\"%02d:%02d\"\n" 0 0 0 59 >> "$TMP1" ++ ++check_decode UTC-1 diff --git a/0023-evaluate-reset-statement-length-context-only-for-set.patch b/0023-evaluate-reset-statement-length-context-only-for-set.patch new file mode 100644 index 0000000..34b466e --- /dev/null +++ b/0023-evaluate-reset-statement-length-context-only-for-set.patch @@ -0,0 +1,218 @@ +From 3f363237125fded5adb4bc9ed8890e518a518286 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:18 +0200 +Subject: [PATCH] evaluate: reset statement length context only for set + mappings + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 57f092a87fc4bc61e29cff31dfff976e1f2005ab + +commit 57f092a87fc4bc61e29cff31dfff976e1f2005ab +Author: Pablo Neira Ayuso +Date: Sun Nov 5 18:33:14 2023 +0100 + + evaluate: reset statement length context only for set mappings + + map expression (which is used a key to look up for the mapping) needs to + consider the statement length context, otherwise incorrect bytecode is + generated when {ct,meta} statement is generated. + + # nft -f - < reg 1 ] + [ bitwise reg 1 = ( reg 1 & 0x0000c00f ) ^ 0x00000000 ] + ... missing byteorder conversion here before shift ... + [ bitwise reg 1 = ( reg 1 >> 0x00000006 ) ] + [ lookup reg 1 set mapv6 dreg 1 ] + [ meta set mark with reg 1 ] + + Reset statement length context only for the mapping side for the + elements in the set. + + Fixes: edecd58755a8 ("evaluate: support shifts larger than the width of the left operand") + Reported-by: Brian Davidson + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/evaluate.c | 2 +- + tests/py/ip6/ip6.t | 5 +++ + tests/py/ip6/ip6.t.json | 58 +++++++++++++++++++++++++++++++++ + tests/py/ip6/ip6.t.payload.inet | 23 +++++++++++++ + tests/py/ip6/ip6.t.payload.ip6 | 19 +++++++++++ + 5 files changed, 106 insertions(+), 1 deletion(-) + +diff --git a/src/evaluate.c b/src/evaluate.c +index 711990a..1850043 100644 +--- a/src/evaluate.c ++++ b/src/evaluate.c +@@ -1913,13 +1913,13 @@ static int expr_evaluate_map(struct eval_ctx *ctx, struct expr **expr) + } + + expr_set_context(&ctx->ectx, NULL, 0); +- ctx->stmt_len = 0; + if (expr_evaluate(ctx, &map->map) < 0) + return -1; + if (expr_is_constant(map->map)) + return expr_error(ctx->msgs, map->map, + "Map expression can not be constant"); + ++ ctx->stmt_len = 0; + mappings = map->mappings; + mappings->set_flags |= NFT_SET_MAP; + +diff --git a/tests/py/ip6/ip6.t b/tests/py/ip6/ip6.t +index 2ffe318..60ea223 100644 +--- a/tests/py/ip6/ip6.t ++++ b/tests/py/ip6/ip6.t +@@ -17,6 +17,11 @@ ip6 dscp != 0x20;ok;ip6 dscp != cs4 + ip6 dscp {cs0, cs1, cs2, cs3, cs4, cs5, cs6, cs7, af11, af12, af13, af21, af22, af23, af31, af32, af33, af41, af42, af43, ef};ok + ip6 dscp vmap { 0x04 : accept, 0x3f : continue } counter;ok + ++!map1 type dscp : mark;ok ++meta mark set ip6 dscp map @map1;ok ++!map2 type dscp . ipv6_addr : mark;ok ++meta mark set ip6 dscp . ip6 daddr map @map2;ok ++ + ip6 flowlabel 22;ok + ip6 flowlabel != 233;ok + - ip6 flowlabel 33-45;ok +diff --git a/tests/py/ip6/ip6.t.json b/tests/py/ip6/ip6.t.json +index cf80217..5411190 100644 +--- a/tests/py/ip6/ip6.t.json ++++ b/tests/py/ip6/ip6.t.json +@@ -135,6 +135,64 @@ + } + ] + ++# meta mark set ip6 dscp map @map1 ++[ ++ { ++ "mangle": { ++ "key": { ++ "meta": { ++ "key": "mark" ++ } ++ }, ++ "value": { ++ "map": { ++ "data": "@map1", ++ "key": { ++ "payload": { ++ "field": "dscp", ++ "protocol": "ip6" ++ } ++ } ++ } ++ } ++ } ++ } ++] ++ ++# meta mark set ip6 dscp . ip6 daddr map @map2 ++[ ++ { ++ "mangle": { ++ "key": { ++ "meta": { ++ "key": "mark" ++ } ++ }, ++ "value": { ++ "map": { ++ "data": "@map2", ++ "key": { ++ "concat": [ ++ { ++ "payload": { ++ "field": "dscp", ++ "protocol": "ip6" ++ } ++ }, ++ { ++ "payload": { ++ "field": "daddr", ++ "protocol": "ip6" ++ } ++ } ++ ] ++ } ++ } ++ } ++ } ++ } ++] ++ + # ip6 flowlabel 22 + [ + { +diff --git a/tests/py/ip6/ip6.t.payload.inet b/tests/py/ip6/ip6.t.payload.inet +index 20dfe54..214a0ed 100644 +--- a/tests/py/ip6/ip6.t.payload.inet ++++ b/tests/py/ip6/ip6.t.payload.inet +@@ -53,6 +53,29 @@ ip6 test-ip6 input + [ lookup reg 1 set __map%d dreg 0 ] + [ counter pkts 0 bytes 0 ] + ++# meta mark set ip6 dscp map @map1 ++inet test-inet input ++ [ meta load nfproto => reg 1 ] ++ [ cmp eq reg 1 0x0000000a ] ++ [ payload load 2b @ network header + 0 => reg 1 ] ++ [ bitwise reg 1 = ( reg 1 & 0x0000c00f ) ^ 0x00000000 ] ++ [ byteorder reg 1 = ntoh(reg 1, 2, 2) ] ++ [ bitwise reg 1 = ( reg 1 >> 0x00000006 ) ] ++ [ lookup reg 1 set map1 dreg 1 ] ++ [ meta set mark with reg 1 ] ++ ++# meta mark set ip6 dscp . ip6 daddr map @map2 ++inet test-inet input ++ [ meta load nfproto => reg 1 ] ++ [ cmp eq reg 1 0x0000000a ] ++ [ payload load 2b @ network header + 0 => reg 1 ] ++ [ bitwise reg 1 = ( reg 1 & 0x0000c00f ) ^ 0x00000000 ] ++ [ byteorder reg 1 = ntoh(reg 1, 2, 2) ] ++ [ bitwise reg 1 = ( reg 1 >> 0x00000006 ) ] ++ [ payload load 16b @ network header + 24 => reg 9 ] ++ [ lookup reg 1 set map2 dreg 1 ] ++ [ meta set mark with reg 1 ] ++ + # ip6 flowlabel 22 + inet test-inet input + [ meta load nfproto => reg 1 ] +diff --git a/tests/py/ip6/ip6.t.payload.ip6 b/tests/py/ip6/ip6.t.payload.ip6 +index f8e3ca3..428b8ea 100644 +--- a/tests/py/ip6/ip6.t.payload.ip6 ++++ b/tests/py/ip6/ip6.t.payload.ip6 +@@ -41,6 +41,25 @@ ip6 test-ip6 input + [ lookup reg 1 set __map%d dreg 0 ] + [ counter pkts 0 bytes 0 ] + ++# meta mark set ip6 dscp map @map1 ++ip6 test-ip6 input ++ [ payload load 2b @ network header + 0 => reg 1 ] ++ [ bitwise reg 1 = ( reg 1 & 0x0000c00f ) ^ 0x00000000 ] ++ [ byteorder reg 1 = ntoh(reg 1, 2, 2) ] ++ [ bitwise reg 1 = ( reg 1 >> 0x00000006 ) ] ++ [ lookup reg 1 set map1 dreg 1 ] ++ [ meta set mark with reg 1 ] ++ ++# meta mark set ip6 dscp . ip6 daddr map @map2 ++ip6 test-ip6 input ++ [ payload load 2b @ network header + 0 => reg 1 ] ++ [ bitwise reg 1 = ( reg 1 & 0x0000c00f ) ^ 0x00000000 ] ++ [ byteorder reg 1 = ntoh(reg 1, 2, 2) ] ++ [ bitwise reg 1 = ( reg 1 >> 0x00000006 ) ] ++ [ payload load 16b @ network header + 24 => reg 9 ] ++ [ lookup reg 1 set map2 dreg 1 ] ++ [ meta set mark with reg 1 ] ++ + # ip6 flowlabel 22 + ip6 test-ip6 input + [ payload load 3b @ network header + 1 => reg 1 ] diff --git a/0024-evaluate-place-byteorder-conversion-before-rshift-in.patch b/0024-evaluate-place-byteorder-conversion-before-rshift-in.patch new file mode 100644 index 0000000..0bfdbc0 --- /dev/null +++ b/0024-evaluate-place-byteorder-conversion-before-rshift-in.patch @@ -0,0 +1,238 @@ +From 2d5e3d1d7241a86d1555b3edfb020f20fc95748d Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:18 +0200 +Subject: [PATCH] evaluate: place byteorder conversion before rshift in payload + expressions + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit cb9b72a43c5684379c027908d9f332170bf8dd15 + +commit cb9b72a43c5684379c027908d9f332170bf8dd15 +Author: Pablo Neira Ayuso +Date: Sun Nov 5 21:54:25 2023 +0100 + + evaluate: place byteorder conversion before rshift in payload expressions + + Use the key from the evaluation context to perform the byteorder + conversion in case that this expression is used for lookups and updates + on explicit sets. + + # nft --debug=netlink add rule ip6 t output ip6 dscp @mapv6 + ip6 t output + [ payload load 2b @ network header + 0 => reg 1 ] + [ bitwise reg 1 = ( reg 1 & 0x0000c00f ) ^ 0x00000000 ] + [ byteorder reg 1 = ntoh(reg 1, 2, 2) ] <-------------- this was missing! + [ bitwise reg 1 = ( reg 1 >> 0x00000006 ) ] + [ lookup reg 1 set mapv6 ] + + Also with set statements (updates from packet path): + + # nft --debug=netlink add rule ip6 t output update @mapv6 { ip6 dscp } + ip6 t output + [ payload load 2b @ network header + 0 => reg 1 ] + [ bitwise reg 1 = ( reg 1 & 0x0000c00f ) ^ 0x00000000 ] + [ byteorder reg 1 = ntoh(reg 1, 2, 2) ] <------------- also here! + [ bitwise reg 1 = ( reg 1 >> 0x00000006 ) ] + [ dynset update reg_key 1 set mapv6 ] + + Simple matches on values and implicit sets rely on the binary transfer + mechanism to propagate the shift to the constant, no explicit byteorder + is required in such case. + + Fixes: 668c18f67203 ("evaluate: place byteorder conversion before rshift in payload statement") + Reported-by: Florian Westphal + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/evaluate.c | 10 +++++++- + tests/py/ip6/ip6.t | 4 ++++ + tests/py/ip6/ip6.t.json | 42 +++++++++++++++++++++++++++++++++ + tests/py/ip6/ip6.t.payload.inet | 21 +++++++++++++++++ + tests/py/ip6/ip6.t.payload.ip6 | 17 +++++++++++++ + 5 files changed, 93 insertions(+), 1 deletion(-) + +diff --git a/src/evaluate.c b/src/evaluate.c +index 1850043..303e36c 100644 +--- a/src/evaluate.c ++++ b/src/evaluate.c +@@ -545,7 +545,8 @@ static void expr_evaluate_bits(struct eval_ctx *ctx, struct expr **exprp) + and->len = masklen; + + if (shift) { +- if (ctx->stmt_len > 0 && div_round_up(masklen, BITS_PER_BYTE) > 1) { ++ if ((ctx->ectx.key || ctx->stmt_len > 0) && ++ div_round_up(masklen, BITS_PER_BYTE) > 1) { + int op = byteorder_conversion_op(expr, BYTEORDER_HOST_ENDIAN); + and = unary_expr_alloc(&expr->location, op, and); + and->len = masklen; +@@ -574,6 +575,7 @@ static void expr_evaluate_bits(struct eval_ctx *ctx, struct expr **exprp) + + static int __expr_evaluate_exthdr(struct eval_ctx *ctx, struct expr **exprp) + { ++ const struct expr *key = ctx->ectx.key; + struct expr *expr = *exprp; + + if (expr->exthdr.flags & NFT_EXTHDR_F_PRESENT) +@@ -582,6 +584,8 @@ static int __expr_evaluate_exthdr(struct eval_ctx *ctx, struct expr **exprp) + if (expr_evaluate_primary(ctx, exprp) < 0) + return -1; + ++ ctx->ectx.key = key; ++ + if (expr->exthdr.offset % BITS_PER_BYTE != 0 || + expr->len % BITS_PER_BYTE != 0) + expr_evaluate_bits(ctx, exprp); +@@ -878,6 +882,7 @@ static bool payload_needs_adjustment(const struct expr *expr) + + static int expr_evaluate_payload(struct eval_ctx *ctx, struct expr **exprp) + { ++ const struct expr *key = ctx->ectx.key; + struct expr *expr = *exprp; + + if (expr->payload.evaluated) +@@ -889,6 +894,8 @@ static int expr_evaluate_payload(struct eval_ctx *ctx, struct expr **exprp) + if (expr_evaluate_primary(ctx, exprp) < 0) + return -1; + ++ ctx->ectx.key = key; ++ + if (payload_needs_adjustment(expr)) + expr_evaluate_bits(ctx, exprp); + +@@ -1508,6 +1515,7 @@ static int expr_evaluate_concat(struct eval_ctx *ctx, struct expr **expr) + } + + __expr_set_context(&ctx->ectx, tmp, bo, dsize, 0); ++ ctx->ectx.key = i; + + if (list_member_evaluate(ctx, &i) < 0) + return -1; +diff --git a/tests/py/ip6/ip6.t b/tests/py/ip6/ip6.t +index 60ea223..430dd57 100644 +--- a/tests/py/ip6/ip6.t ++++ b/tests/py/ip6/ip6.t +@@ -21,6 +21,10 @@ ip6 dscp vmap { 0x04 : accept, 0x3f : continue } counter;ok + meta mark set ip6 dscp map @map1;ok + !map2 type dscp . ipv6_addr : mark;ok + meta mark set ip6 dscp . ip6 daddr map @map2;ok ++!map3 type dscp : mark;ok ++ip6 dscp @map3;ok ++!map4 type dscp . ipv6_addr : mark;ok ++ip6 dscp . ip6 daddr @map4;ok + + ip6 flowlabel 22;ok + ip6 flowlabel != 233;ok +diff --git a/tests/py/ip6/ip6.t.json b/tests/py/ip6/ip6.t.json +index 5411190..49e5a2d 100644 +--- a/tests/py/ip6/ip6.t.json ++++ b/tests/py/ip6/ip6.t.json +@@ -193,6 +193,48 @@ + } + ] + ++# ip6 dscp @map3 ++[ ++ { ++ "match": { ++ "left": { ++ "payload": { ++ "field": "dscp", ++ "protocol": "ip6" ++ } ++ }, ++ "op": "==", ++ "right": "@map3" ++ } ++ } ++] ++ ++# ip6 dscp . ip6 daddr @map4 ++[ ++ { ++ "match": { ++ "left": { ++ "concat": [ ++ { ++ "payload": { ++ "field": "dscp", ++ "protocol": "ip6" ++ } ++ }, ++ { ++ "payload": { ++ "field": "daddr", ++ "protocol": "ip6" ++ } ++ } ++ ] ++ }, ++ "op": "==", ++ "right": "@map4" ++ } ++ } ++] ++ + # ip6 flowlabel 22 + [ + { +diff --git a/tests/py/ip6/ip6.t.payload.inet b/tests/py/ip6/ip6.t.payload.inet +index 214a0ed..dbb430a 100644 +--- a/tests/py/ip6/ip6.t.payload.inet ++++ b/tests/py/ip6/ip6.t.payload.inet +@@ -76,6 +76,27 @@ inet test-inet input + [ lookup reg 1 set map2 dreg 1 ] + [ meta set mark with reg 1 ] + ++# ip6 dscp @map3 ++inet test-inet input ++ [ meta load nfproto => reg 1 ] ++ [ cmp eq reg 1 0x0000000a ] ++ [ payload load 2b @ network header + 0 => reg 1 ] ++ [ bitwise reg 1 = ( reg 1 & 0x0000c00f ) ^ 0x00000000 ] ++ [ byteorder reg 1 = ntoh(reg 1, 2, 2) ] ++ [ bitwise reg 1 = ( reg 1 >> 0x00000006 ) ] ++ [ lookup reg 1 set map3 ] ++ ++# ip6 dscp . ip6 daddr @map4 ++inet test-inet input ++ [ meta load nfproto => reg 1 ] ++ [ cmp eq reg 1 0x0000000a ] ++ [ payload load 2b @ network header + 0 => reg 1 ] ++ [ bitwise reg 1 = ( reg 1 & 0x0000c00f ) ^ 0x00000000 ] ++ [ byteorder reg 1 = ntoh(reg 1, 2, 2) ] ++ [ bitwise reg 1 = ( reg 1 >> 0x00000006 ) ] ++ [ payload load 16b @ network header + 24 => reg 9 ] ++ [ lookup reg 1 set map4 ] ++ + # ip6 flowlabel 22 + inet test-inet input + [ meta load nfproto => reg 1 ] +diff --git a/tests/py/ip6/ip6.t.payload.ip6 b/tests/py/ip6/ip6.t.payload.ip6 +index 428b8ea..b128923 100644 +--- a/tests/py/ip6/ip6.t.payload.ip6 ++++ b/tests/py/ip6/ip6.t.payload.ip6 +@@ -60,6 +60,23 @@ ip6 test-ip6 input + [ lookup reg 1 set map2 dreg 1 ] + [ meta set mark with reg 1 ] + ++# ip6 dscp @map3 ++ip6 test-ip6 input ++ [ payload load 2b @ network header + 0 => reg 1 ] ++ [ bitwise reg 1 = ( reg 1 & 0x0000c00f ) ^ 0x00000000 ] ++ [ byteorder reg 1 = ntoh(reg 1, 2, 2) ] ++ [ bitwise reg 1 = ( reg 1 >> 0x00000006 ) ] ++ [ lookup reg 1 set map3 ] ++ ++# ip6 dscp . ip6 daddr @map4 ++ip6 test-ip6 input ++ [ payload load 2b @ network header + 0 => reg 1 ] ++ [ bitwise reg 1 = ( reg 1 & 0x0000c00f ) ^ 0x00000000 ] ++ [ byteorder reg 1 = ntoh(reg 1, 2, 2) ] ++ [ bitwise reg 1 = ( reg 1 >> 0x00000006 ) ] ++ [ payload load 16b @ network header + 24 => reg 9 ] ++ [ lookup reg 1 set map4 ] ++ + # ip6 flowlabel 22 + ip6 test-ip6 input + [ payload load 3b @ network header + 1 => reg 1 ] diff --git a/0025-src-add-free_const-and-use-it-instead-of-xfree.patch b/0025-src-add-free_const-and-use-it-instead-of-xfree.patch new file mode 100644 index 0000000..1252bf0 --- /dev/null +++ b/0025-src-add-free_const-and-use-it-instead-of-xfree.patch @@ -0,0 +1,992 @@ +From 612339e3a96884fa9436ec56cfae355356a745e8 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:18 +0200 +Subject: [PATCH] src: add free_const() and use it instead of xfree() + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit ffd6b4790a728bd879cc8e4532b54150febb58fa +Conflicts: One chunk omitted due to previous backport of commit + 4955ae1a81b73 ("Add support for table's persist flag") + +commit ffd6b4790a728bd879cc8e4532b54150febb58fa +Author: Thomas Haller +Date: Tue Oct 24 11:57:09 2023 +0200 + + src: add free_const() and use it instead of xfree() + + Almost everywhere xmalloc() and friends is used instead of malloc(). + This is almost everywhere paired with xfree(). + + xfree() has two problems. First, it brings the wrong notion that + xmalloc() should be paired with xfree(), as if xmalloc() would not use + the plain malloc() allocator. In practices, xfree() just wraps free(), + and it wouldn't make sense any other way. xfree() should go away. This + will be addressed in the next commit. + + The problem addressed by this commit is that xfree() accepts a const + pointer. Paired with the practice of almost always using xfree() instead + of free(), all our calls to xfree() cast away constness of the pointer, + regardless whether that is necessary. Declaring a pointer as const + should help us to catch wrong uses. If the xfree() function always casts + aways const, the compiler doesn't help. + + There are many places that rightly cast away const during free. But not + all of them. Add a free_const() macro, which is like free(), but accepts + const pointers. We should always make an intentional choice whether to + use free() or free_const(). Having a free_const() macro makes this very + common choice clearer, instead of adding a (void*) cast at many places. + + Note that we now pair xmalloc() allocations with a free() call (instead + of xfree(). That inconsistency will be resolved in the next commit. + + Signed-off-by: Thomas Haller + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + include/nft.h | 6 ++ + src/ct.c | 2 +- + src/datatype.c | 8 +-- + src/evaluate.c | 8 +-- + src/expression.c | 4 +- + src/libnftables.c | 12 ++-- + src/mnl.c | 12 ++-- + src/optimize.c | 2 +- + src/parser_bison.y | 138 ++++++++++++++++++++++----------------------- + src/rule.c | 36 ++++++------ + src/scanner.l | 4 +- + src/statement.c | 2 +- + src/xt.c | 2 +- + 13 files changed, 121 insertions(+), 115 deletions(-) + +diff --git a/include/nft.h b/include/nft.h +index 3c894e5..a2d62db 100644 +--- a/include/nft.h ++++ b/include/nft.h +@@ -9,4 +9,10 @@ + #include + #include + ++/* Just free(), but casts to a (void*). This is for places where ++ * we have a const pointer that we know we want to free. We could just ++ * do the (void*) cast, but free_const() makes it clear that this is ++ * something we frequently need to do and it's intentional. */ ++#define free_const(ptr) free((void *)(ptr)) ++ + #endif /* NFTABLES_NFT_H */ +diff --git a/src/ct.c b/src/ct.c +index 1dda799..ebfd90a 100644 +--- a/src/ct.c ++++ b/src/ct.c +@@ -570,7 +570,7 @@ static void flow_offload_stmt_print(const struct stmt *stmt, + + static void flow_offload_stmt_destroy(struct stmt *stmt) + { +- xfree(stmt->flow.table_name); ++ free_const(stmt->flow.table_name); + } + + static const struct stmt_ops flow_offload_stmt_ops = { +diff --git a/src/datatype.c b/src/datatype.c +index 9530ae7..9353ff2 100644 +--- a/src/datatype.c ++++ b/src/datatype.c +@@ -938,8 +938,8 @@ void rt_symbol_table_free(const struct symbol_table *tbl) + const struct symbolic_constant *s; + + for (s = tbl->symbols; s->identifier != NULL; s++) +- xfree(s->identifier); +- xfree(tbl); ++ free_const(s->identifier); ++ free_const(tbl); + } + + void mark_table_init(struct nft_ctx *ctx) +@@ -1296,8 +1296,8 @@ void datatype_free(const struct datatype *ptr) + if (--dtype->refcnt > 0) + return; + +- xfree(dtype->name); +- xfree(dtype->desc); ++ free_const(dtype->name); ++ free_const(dtype->desc); + xfree(dtype); + } + +diff --git a/src/evaluate.c b/src/evaluate.c +index 303e36c..c41210b 100644 +--- a/src/evaluate.c ++++ b/src/evaluate.c +@@ -4022,7 +4022,7 @@ static int stmt_evaluate_chain(struct eval_ctx *ctx, struct stmt *stmt) + memset(&h, 0, sizeof(h)); + handle_merge(&h, &chain->handle); + h.family = ctx->rule->handle.family; +- xfree(h.table.name); ++ free_const(h.table.name); + h.table.name = xstrdup(ctx->rule->handle.table.name); + h.chain.location = stmt->location; + h.chain_id = chain->handle.chain_id; +@@ -4042,9 +4042,9 @@ static int stmt_evaluate_chain(struct eval_ctx *ctx, struct stmt *stmt) + struct handle h2 = {}; + + handle_merge(&rule->handle, &ctx->rule->handle); +- xfree(rule->handle.table.name); ++ free_const(rule->handle.table.name); + rule->handle.table.name = xstrdup(ctx->rule->handle.table.name); +- xfree(rule->handle.chain.name); ++ free_const(rule->handle.chain.name); + rule->handle.chain.name = NULL; + rule->handle.chain_id = chain->handle.chain_id; + if (rule_evaluate(&rule_ctx, rule, CMD_INVALID) < 0) +@@ -5147,7 +5147,7 @@ static int ct_timeout_evaluate(struct eval_ctx *ctx, struct obj *obj) + + ct->timeout[ts->timeout_index] = ts->timeout_value; + list_del(&ts->head); +- xfree(ts->timeout_str); ++ free_const(ts->timeout_str); + xfree(ts); + } + +diff --git a/src/expression.c b/src/expression.c +index a21dfec..0b4a537 100644 +--- a/src/expression.c ++++ b/src/expression.c +@@ -314,7 +314,7 @@ static void symbol_expr_clone(struct expr *new, const struct expr *expr) + + static void symbol_expr_destroy(struct expr *expr) + { +- xfree(expr->identifier); ++ free_const(expr->identifier); + } + + static const struct expr_ops symbol_expr_ops = { +@@ -1335,7 +1335,7 @@ static void set_elem_expr_destroy(struct expr *expr) + { + struct stmt *stmt, *next; + +- xfree(expr->comment); ++ free_const(expr->comment); + expr_free(expr->key); + list_for_each_entry_safe(stmt, next, &expr->stmt_list, list) + stmt_free(stmt); +diff --git a/src/libnftables.c b/src/libnftables.c +index 41f54c0..866b5c6 100644 +--- a/src/libnftables.c ++++ b/src/libnftables.c +@@ -154,8 +154,8 @@ void nft_ctx_clear_vars(struct nft_ctx *ctx) + unsigned int i; + + for (i = 0; i < ctx->num_vars; i++) { +- xfree(ctx->vars[i].key); +- xfree(ctx->vars[i].value); ++ free_const(ctx->vars[i].key); ++ free_const(ctx->vars[i].value); + } + ctx->num_vars = 0; + xfree(ctx->vars); +@@ -743,12 +743,12 @@ err: + + list_for_each_entry_safe(indesc, next, &nft->vars_ctx.indesc_list, list) { + if (indesc->name) +- xfree(indesc->name); ++ free_const(indesc->name); + + xfree(indesc); + } + } +- xfree(nft->vars_ctx.buf); ++ free_const(nft->vars_ctx.buf); + + if (!rc && + nft_output_json(&nft->output) && +@@ -799,12 +799,12 @@ int nft_run_cmd_from_filename(struct nft_ctx *nft, const char *filename) + + if (nft->optimize_flags) { + ret = nft_run_optimized_file(nft, filename); +- xfree(nft->stdin_buf); ++ free_const(nft->stdin_buf); + return ret; + } + + ret = __nft_run_cmd_from_filename(nft, filename); +- xfree(nft->stdin_buf); ++ free_const(nft->stdin_buf); + + return ret; + } +diff --git a/src/mnl.c b/src/mnl.c +index 0fb36bd..0158924 100644 +--- a/src/mnl.c ++++ b/src/mnl.c +@@ -776,9 +776,9 @@ static void nft_dev_array_free(const struct nft_dev *dev_array) + int i = 0; + + while (dev_array[i].ifname != NULL) +- xfree(dev_array[i++].ifname); ++ free_const(dev_array[i++].ifname); + +- xfree(dev_array); ++ free_const(dev_array); + } + + static void mnl_nft_chain_devs_build(struct nlmsghdr *nlh, struct cmd *cmd) +@@ -2175,10 +2175,10 @@ static struct basehook *basehook_alloc(void) + static void basehook_free(struct basehook *b) + { + list_del(&b->list); +- xfree(b->module_name); +- xfree(b->hookfn); +- xfree(b->chain); +- xfree(b->table); ++ free_const(b->module_name); ++ free_const(b->hookfn); ++ free_const(b->chain); ++ free_const(b->table); + xfree(b); + } + +diff --git a/src/optimize.c b/src/optimize.c +index 27e0ffe..9ae9283 100644 +--- a/src/optimize.c ++++ b/src/optimize.c +@@ -1194,7 +1194,7 @@ static void merge_rules(const struct optimize_ctx *ctx, + } + + if (ctx->rule[from]->comment) { +- xfree(ctx->rule[from]->comment); ++ free_const(ctx->rule[from]->comment); + ctx->rule[from]->comment = NULL; + } + +diff --git a/src/parser_bison.y b/src/parser_bison.y +index 5c2d14b..b485a48 100644 +--- a/src/parser_bison.y ++++ b/src/parser_bison.y +@@ -154,13 +154,13 @@ static struct expr *ifname_expr_alloc(const struct location *location, + struct expr *expr; + + if (length == 0) { +- xfree(name); ++ free_const(name); + erec_queue(error(location, "empty interface name"), queue); + return NULL; + } + + if (length >= IFNAMSIZ) { +- xfree(name); ++ free_const(name); + erec_queue(error(location, "interface name too long"), queue); + return NULL; + } +@@ -168,7 +168,7 @@ static struct expr *ifname_expr_alloc(const struct location *location, + expr = constant_expr_alloc(location, &ifname_type, BYTEORDER_HOST_ENDIAN, + length * BITS_PER_BYTE, name); + +- xfree(name); ++ free_const(name); + + return expr; + } +@@ -358,7 +358,7 @@ int nft_lex(void *, void *, void *); + %token STRING "string" + %token QUOTED_STRING "quoted string" + %token ASTERISK_STRING "string with a trailing asterisk" +-%destructor { xfree($$); } STRING QUOTED_STRING ASTERISK_STRING ++%destructor { free_const($$); } STRING QUOTED_STRING ASTERISK_STRING + + %token LL_HDR "ll" + %token NETWORK_HDR "nh" +@@ -674,7 +674,7 @@ int nft_lex(void *, void *, void *); + %type limit_rate_bytes + + %type identifier type_identifier string comment_spec +-%destructor { xfree($$); } identifier type_identifier string comment_spec ++%destructor { free_const($$); } identifier type_identifier string comment_spec + + %type time_spec time_spec_or_num_s quota_used + +@@ -709,7 +709,7 @@ int nft_lex(void *, void *, void *); + %type int_num chain_policy + %type extended_prio_spec prio_spec + %type extended_prio_name quota_unit basehook_device_name +-%destructor { xfree($$); } extended_prio_name quota_unit basehook_device_name ++%destructor { free_const($$); } extended_prio_name quota_unit basehook_device_name + + %type dev_spec + %destructor { xfree($$); } dev_spec +@@ -930,7 +930,7 @@ int nft_lex(void *, void *, void *); + + %type markup_format + %type monitor_event +-%destructor { xfree($$); } monitor_event ++%destructor { free_const($$); } monitor_event + %type monitor_object monitor_format + + %type synproxy_ts synproxy_sack +@@ -1055,10 +1055,10 @@ close_scope_xt : { scanner_pop_start_cond(nft->scanner, PARSER_SC_XT); } + common_block : INCLUDE QUOTED_STRING stmt_separator + { + if (scanner_include_file(nft, scanner, $2, &@$) < 0) { +- xfree($2); ++ free_const($2); + YYERROR; + } +- xfree($2); ++ free_const($2); + } + | DEFINE identifier '=' initializer_expr stmt_separator + { +@@ -1068,19 +1068,19 @@ common_block : INCLUDE QUOTED_STRING stmt_separator + erec_queue(error(&@2, "redefinition of symbol '%s'", $2), + state->msgs); + expr_free($4); +- xfree($2); ++ free_const($2); + YYERROR; + } + + symbol_bind(scope, $2, $4); +- xfree($2); ++ free_const($2); + } + | REDEFINE identifier '=' initializer_expr stmt_separator + { + struct scope *scope = current_scope(state); + + symbol_bind(scope, $2, $4); +- xfree($2); ++ free_const($2); + } + | UNDEFINE identifier stmt_separator + { +@@ -1089,10 +1089,10 @@ common_block : INCLUDE QUOTED_STRING stmt_separator + if (symbol_unbind(scope, $2) < 0) { + erec_queue(error(&@2, "undefined symbol '%s'", $2), + state->msgs); +- xfree($2); ++ free_const($2); + YYERROR; + } +- xfree($2); ++ free_const($2); + } + | error stmt_separator + { +@@ -1884,7 +1884,7 @@ table_options : FLAGS table_flags + | comment_spec + { + if (already_set($0->comment, &@$, state)) { +- xfree($1); ++ free_const($1); + YYERROR; + } + $
0->comment = $1; +@@ -2073,7 +2073,7 @@ chain_block : /* empty */ { $$ = $-1; } + | chain_block comment_spec stmt_separator + { + if (already_set($1->comment, &@2, state)) { +- xfree($2); ++ free_const($2); + YYERROR; + } + $1->comment = $2; +@@ -2199,7 +2199,7 @@ set_block : /* empty */ { $$ = $-1; } + | set_block comment_spec stmt_separator + { + if (already_set($1->comment, &@2, state)) { +- xfree($2); ++ free_const($2); + YYERROR; + } + $1->comment = $2; +@@ -2316,7 +2316,7 @@ map_block : /* empty */ { $$ = $-1; } + | map_block comment_spec stmt_separator + { + if (already_set($1->comment, &@2, state)) { +- xfree($2); ++ free_const($2); + YYERROR; + } + $1->comment = $2; +@@ -2355,10 +2355,10 @@ flowtable_block : /* empty */ { $$ = $-1; } + if ($$->hook.name == NULL) { + erec_queue(error(&@3, "unknown chain hook"), + state->msgs); +- xfree($3); ++ free_const($3); + YYERROR; + } +- xfree($3); ++ free_const($3); + + $$->priority = $4; + } +@@ -2432,12 +2432,12 @@ data_type_atom_expr : type_identifier + if (dtype == NULL) { + erec_queue(error(&@1, "unknown datatype %s", $1), + state->msgs); +- xfree($1); ++ free_const($1); + YYERROR; + } + $$ = constant_expr_alloc(&@1, dtype, dtype->byteorder, + dtype->size, NULL); +- xfree($1); ++ free_const($1); + } + | TIME + { +@@ -2474,7 +2474,7 @@ counter_block : /* empty */ { $$ = $-1; } + | counter_block comment_spec + { + if (already_set($1->comment, &@2, state)) { +- xfree($2); ++ free_const($2); + YYERROR; + } + $1->comment = $2; +@@ -2491,7 +2491,7 @@ quota_block : /* empty */ { $$ = $-1; } + | quota_block comment_spec + { + if (already_set($1->comment, &@2, state)) { +- xfree($2); ++ free_const($2); + YYERROR; + } + $1->comment = $2; +@@ -2508,7 +2508,7 @@ ct_helper_block : /* empty */ { $$ = $-1; } + | ct_helper_block comment_spec + { + if (already_set($1->comment, &@2, state)) { +- xfree($2); ++ free_const($2); + YYERROR; + } + $1->comment = $2; +@@ -2529,7 +2529,7 @@ ct_timeout_block : /*empty */ + | ct_timeout_block comment_spec + { + if (already_set($1->comment, &@2, state)) { +- xfree($2); ++ free_const($2); + YYERROR; + } + $1->comment = $2; +@@ -2546,7 +2546,7 @@ ct_expect_block : /*empty */ { $$ = $-1; } + | ct_expect_block comment_spec + { + if (already_set($1->comment, &@2, state)) { +- xfree($2); ++ free_const($2); + YYERROR; + } + $1->comment = $2; +@@ -2563,7 +2563,7 @@ limit_block : /* empty */ { $$ = $-1; } + | limit_block comment_spec + { + if (already_set($1->comment, &@2, state)) { +- xfree($2); ++ free_const($2); + YYERROR; + } + $1->comment = $2; +@@ -2580,7 +2580,7 @@ secmark_block : /* empty */ { $$ = $-1; } + | secmark_block comment_spec + { + if (already_set($1->comment, &@2, state)) { +- xfree($2); ++ free_const($2); + YYERROR; + } + $1->comment = $2; +@@ -2597,7 +2597,7 @@ synproxy_block : /* empty */ { $$ = $-1; } + | synproxy_block comment_spec + { + if (already_set($1->comment, &@2, state)) { +- xfree($2); ++ free_const($2); + YYERROR; + } + $1->comment = $2; +@@ -2618,12 +2618,12 @@ hook_spec : TYPE close_scope_type STRING HOOK STRING dev_spec prio_spec + if (chain_type == NULL) { + erec_queue(error(&@3, "unknown chain type"), + state->msgs); +- xfree($3); ++ free_const($3); + YYERROR; + } + $0->type.loc = @3; + $0->type.str = xstrdup(chain_type); +- xfree($3); ++ free_const($3); + + $0->loc = @$; + $0->hook.loc = @5; +@@ -2631,10 +2631,10 @@ hook_spec : TYPE close_scope_type STRING HOOK STRING dev_spec prio_spec + if ($0->hook.name == NULL) { + erec_queue(error(&@5, "unknown chain hook"), + state->msgs); +- xfree($5); ++ free_const($5); + YYERROR; + } +- xfree($5); ++ free_const($5); + + $0->dev_expr = $6; + $0->priority = $7; +@@ -2681,7 +2681,7 @@ extended_prio_spec : int_num + BYTEORDER_HOST_ENDIAN, + strlen($1) * BITS_PER_BYTE, + $1); +- xfree($1); ++ free_const($1); + $$ = spec; + } + | extended_prio_name PLUS NUM +@@ -2694,7 +2694,7 @@ extended_prio_spec : int_num + BYTEORDER_HOST_ENDIAN, + strlen(str) * BITS_PER_BYTE, + str); +- xfree($1); ++ free_const($1); + $$ = spec; + } + | extended_prio_name DASH NUM +@@ -2707,7 +2707,7 @@ extended_prio_spec : int_num + BYTEORDER_HOST_ENDIAN, + strlen(str) * BITS_PER_BYTE, + str); +- xfree($1); ++ free_const($1); + $$ = spec; + } + ; +@@ -2792,7 +2792,7 @@ time_spec : STRING + uint64_t res; + + erec = time_parse(&@1, $1, &res); +- xfree($1); ++ free_const($1); + if (erec != NULL) { + erec_queue(erec, state->msgs); + YYERROR; +@@ -2993,7 +2993,7 @@ comment_spec : COMMENT string + erec_queue(error(&@2, "comment too long, %d characters maximum allowed", + NFTNL_UDATA_COMMENT_MAXLEN), + state->msgs); +- xfree($2); ++ free_const($2); + YYERROR; + } + $$ = $2; +@@ -3094,8 +3094,8 @@ stmt : verdict_stmt + xt_stmt : XT STRING string + { + $$ = NULL; +- xfree($2); +- xfree($3); ++ free_const($2); ++ free_const($3); + erec_queue(error(&@$, "unsupported xtables compat expression, use iptables-nft with this ruleset"), + state->msgs); + YYERROR; +@@ -3253,7 +3253,7 @@ log_arg : PREFIX string + expr = constant_expr_alloc(&@$, &string_type, + BYTEORDER_HOST_ENDIAN, + (strlen($2) + 1) * BITS_PER_BYTE, $2); +- xfree($2); ++ free_const($2); + $0->log.prefix = expr; + $0->log.flags |= STMT_LOG_PREFIX; + break; +@@ -3327,7 +3327,7 @@ log_arg : PREFIX string + state->msgs); + } + expr_free(expr); +- xfree($2); ++ free_const($2); + YYERROR; + } + item = variable_expr_alloc(&@$, scope, sym); +@@ -3357,7 +3357,7 @@ log_arg : PREFIX string + } + } + +- xfree($2); ++ free_const($2); + $0->log.prefix = expr; + $0->log.flags |= STMT_LOG_PREFIX; + } +@@ -3410,10 +3410,10 @@ level_type : string + else { + erec_queue(error(&@1, "invalid log level"), + state->msgs); +- xfree($1); ++ free_const($1); + YYERROR; + } +- xfree($1); ++ free_const($1); + } + ; + +@@ -3503,7 +3503,7 @@ quota_used : /* empty */ { $$ = 0; } + uint64_t rate; + + erec = data_unit_parse(&@$, $3, &rate); +- xfree($3); ++ free_const($3); + if (erec != NULL) { + erec_queue(erec, state->msgs); + YYERROR; +@@ -3518,7 +3518,7 @@ quota_stmt : QUOTA quota_mode NUM quota_unit quota_used close_scope_quota + uint64_t rate; + + erec = data_unit_parse(&@$, $4, &rate); +- xfree($4); ++ free_const($4); + if (erec != NULL) { + erec_queue(erec, state->msgs); + YYERROR; +@@ -3562,7 +3562,7 @@ limit_rate_bytes : NUM STRING + uint64_t rate, unit; + + erec = rate_parse(&@$, $2, &rate, &unit); +- xfree($2); ++ free_const($2); + if (erec != NULL) { + erec_queue(erec, state->msgs); + YYERROR; +@@ -3584,7 +3584,7 @@ limit_bytes : NUM BYTES { $$ = $1; } + uint64_t rate; + + erec = data_unit_parse(&@$, $2, &rate); +- xfree($2); ++ free_const($2); + if (erec != NULL) { + erec_queue(erec, state->msgs); + YYERROR; +@@ -3613,7 +3613,7 @@ reject_with_expr : STRING + { + $$ = symbol_expr_alloc(&@$, SYMBOL_VALUE, + current_scope(state), $1); +- xfree($1); ++ free_const($1); + } + | integer_expr { $$ = $1; } + ; +@@ -4277,12 +4277,12 @@ variable_expr : '$' identifier + erec_queue(error(&@2, "unknown identifier '%s'", $2), + state->msgs); + } +- xfree($2); ++ free_const($2); + YYERROR; + } + + $$ = variable_expr_alloc(&@$, scope, sym); +- xfree($2); ++ free_const($2); + } + ; + +@@ -4292,7 +4292,7 @@ symbol_expr : variable_expr + $$ = symbol_expr_alloc(&@$, SYMBOL_VALUE, + current_scope(state), + $1); +- xfree($1); ++ free_const($1); + } + ; + +@@ -4305,7 +4305,7 @@ set_ref_symbol_expr : AT identifier close_scope_at + $$ = symbol_expr_alloc(&@$, SYMBOL_SET, + current_scope(state), + $2); +- xfree($2); ++ free_const($2); + } + ; + +@@ -4402,10 +4402,10 @@ osf_ttl : /* empty */ + else { + erec_queue(error(&@2, "invalid ttl option"), + state->msgs); +- xfree($2); ++ free_const($2); + YYERROR; + } +- xfree($2); ++ free_const($2); + } + ; + +@@ -4575,7 +4575,7 @@ set_elem_option : TIMEOUT time_spec + | comment_spec + { + if (already_set($0->comment, &@1, state)) { +- xfree($1); ++ free_const($1); + YYERROR; + } + $0->comment = $1; +@@ -4657,7 +4657,7 @@ set_elem_stmt : COUNTER close_scope_counter + uint64_t rate; + + erec = data_unit_parse(&@$, $4, &rate); +- xfree($4); ++ free_const($4); + if (erec != NULL) { + erec_queue(erec, state->msgs); + YYERROR; +@@ -4690,7 +4690,7 @@ set_elem_expr_option : TIMEOUT time_spec + | comment_spec + { + if (already_set($0->comment, &@1, state)) { +- xfree($1); ++ free_const($1); + YYERROR; + } + $0->comment = $1; +@@ -4742,7 +4742,7 @@ quota_config : quota_mode NUM quota_unit quota_used + uint64_t rate; + + erec = data_unit_parse(&@$, $3, &rate); +- xfree($3); ++ free_const($3); + if (erec != NULL) { + erec_queue(erec, state->msgs); + YYERROR; +@@ -4771,10 +4771,10 @@ secmark_config : string + ret = snprintf(secmark->ctx, sizeof(secmark->ctx), "%s", $1); + if (ret <= 0 || ret >= (int)sizeof(secmark->ctx)) { + erec_queue(error(&@1, "invalid context '%s', max length is %u\n", $1, (int)sizeof(secmark->ctx)), state->msgs); +- xfree($1); ++ free_const($1); + YYERROR; + } +- xfree($1); ++ free_const($1); + } + ; + +@@ -4811,7 +4811,7 @@ ct_helper_config : TYPE QUOTED_STRING PROTOCOL ct_l4protoname stmt_separator cl + erec_queue(error(&@2, "invalid name '%s', max length is %u\n", $2, (int)sizeof(ct->name)), state->msgs); + YYERROR; + } +- xfree($2); ++ free_const($2); + + ct->l4proto = $4; + } +@@ -5206,7 +5206,7 @@ chain_expr : variable_expr + BYTEORDER_HOST_ENDIAN, + strlen($1) * BITS_PER_BYTE, + $1); +- xfree($1); ++ free_const($1); + } + ; + +@@ -5224,7 +5224,7 @@ meta_expr : META meta_key close_scope_meta + unsigned int key; + + erec = meta_key_parse(&@$, $2, &key); +- xfree($2); ++ free_const($2); + if (erec != NULL) { + erec_queue(erec, state->msgs); + YYERROR; +@@ -5301,7 +5301,7 @@ meta_stmt : META meta_key SET stmt_expr close_scope_meta + unsigned int key; + + erec = meta_key_parse(&@$, $2, &key); +- xfree($2); ++ free_const($2); + if (erec != NULL) { + erec_queue(erec, state->msgs); + YYERROR; +@@ -5612,10 +5612,10 @@ payload_base_spec : LL_HDR { $$ = PROTO_BASE_LL_HDR; } + $$ = PROTO_BASE_INNER_HDR; + } else { + erec_queue(error(&@1, "unknown raw payload base"), state->msgs); +- xfree($1); ++ free_const($1); + YYERROR; + } +- xfree($1); ++ free_const($1); + } + ; + +diff --git a/src/rule.c b/src/rule.c +index a0e151d..76cd58c 100644 +--- a/src/rule.c ++++ b/src/rule.c +@@ -104,11 +104,11 @@ int timeout_str2num(uint16_t l4proto, struct timeout_state *ts) + + void handle_free(struct handle *h) + { +- xfree(h->table.name); +- xfree(h->chain.name); +- xfree(h->set.name); +- xfree(h->flowtable.name); +- xfree(h->obj.name); ++ free_const(h->table.name); ++ free_const(h->chain.name); ++ free_const(h->set.name); ++ free_const(h->flowtable.name); ++ free_const(h->obj.name); + } + + void handle_merge(struct handle *dst, const struct handle *src) +@@ -194,7 +194,7 @@ void set_free(struct set *set) + + expr_free(set->init); + if (set->comment) +- xfree(set->comment); ++ free_const(set->comment); + handle_free(&set->handle); + list_for_each_entry_safe(stmt, next, &set->stmt_list, list) + stmt_free(stmt); +@@ -479,7 +479,7 @@ void rule_free(struct rule *rule) + return; + stmt_list_free(&rule->stmts); + handle_free(&rule->handle); +- xfree(rule->comment); ++ free_const(rule->comment); + xfree(rule); + } + +@@ -557,7 +557,7 @@ void scope_release(const struct scope *scope) + list_for_each_entry_safe(sym, next, &scope->symbols, list) { + assert(sym->refcnt == 1); + list_del(&sym->list); +- xfree(sym->identifier); ++ free_const(sym->identifier); + expr_free(sym->expr); + xfree(sym); + } +@@ -597,7 +597,7 @@ struct symbol *symbol_get(const struct scope *scope, const char *identifier) + static void symbol_put(struct symbol *sym) + { + if (--sym->refcnt == 0) { +- xfree(sym->identifier); ++ free_const(sym->identifier); + expr_free(sym->expr); + xfree(sym); + } +@@ -730,14 +730,14 @@ void chain_free(struct chain *chain) + rule_free(rule); + handle_free(&chain->handle); + scope_release(&chain->scope); +- xfree(chain->type.str); ++ free_const(chain->type.str); + expr_free(chain->dev_expr); + for (i = 0; i < chain->dev_array_len; i++) +- xfree(chain->dev_array[i]); ++ free_const(chain->dev_array[i]); + xfree(chain->dev_array); + expr_free(chain->priority.expr); + expr_free(chain->policy); +- xfree(chain->comment); ++ free_const(chain->comment); + xfree(chain); + } + +@@ -1151,7 +1151,7 @@ void table_free(struct table *table) + if (--table->refcnt > 0) + return; + if (table->comment) +- xfree(table->comment); ++ free_const(table->comment); + list_for_each_entry_safe(chain, next, &table->chains, list) + chain_free(chain); + list_for_each_entry_safe(chain, next, &table->chain_bindings, cache.list) +@@ -1360,7 +1360,7 @@ struct monitor *monitor_alloc(uint32_t format, uint32_t type, const char *event) + + void monitor_free(struct monitor *m) + { +- xfree(m->event); ++ free_const(m->event); + xfree(m); + } + +@@ -1416,7 +1416,7 @@ void cmd_free(struct cmd *cmd) + } + } + xfree(cmd->attr); +- xfree(cmd->arg); ++ free_const(cmd->arg); + xfree(cmd); + } + +@@ -1654,14 +1654,14 @@ void obj_free(struct obj *obj) + { + if (--obj->refcnt > 0) + return; +- xfree(obj->comment); ++ free_const(obj->comment); + handle_free(&obj->handle); + if (obj->type == NFT_OBJECT_CT_TIMEOUT) { + struct timeout_state *ts, *next; + + list_for_each_entry_safe(ts, next, &obj->ct_timeout.timeout_list, head) { + list_del(&ts->head); +- xfree(ts->timeout_str); ++ free_const(ts->timeout_str); + xfree(ts); + } + } +@@ -2074,7 +2074,7 @@ void flowtable_free(struct flowtable *flowtable) + + if (flowtable->dev_array != NULL) { + for (i = 0; i < flowtable->dev_array_len; i++) +- xfree(flowtable->dev_array[i]); ++ free_const(flowtable->dev_array[i]); + xfree(flowtable->dev_array); + } + xfree(flowtable); +diff --git a/src/scanner.l b/src/scanner.l +index 88376b7..93a31f2 100644 +--- a/src/scanner.l ++++ b/src/scanner.l +@@ -1261,8 +1261,8 @@ void *scanner_init(struct parser_state *state) + static void input_descriptor_destroy(const struct input_descriptor *indesc) + { + if (indesc->name) +- xfree(indesc->name); +- xfree(indesc); ++ free_const(indesc->name); ++ free_const(indesc); + } + + static void input_descriptor_list_destroy(struct parser_state *state) +diff --git a/src/statement.c b/src/statement.c +index f5176e6..994b522 100644 +--- a/src/statement.c ++++ b/src/statement.c +@@ -183,7 +183,7 @@ static void meter_stmt_destroy(struct stmt *stmt) + expr_free(stmt->meter.key); + expr_free(stmt->meter.set); + stmt_free(stmt->meter.stmt); +- xfree(stmt->meter.name); ++ free_const(stmt->meter.name); + } + + static const struct stmt_ops meter_stmt_ops = { +diff --git a/src/xt.c b/src/xt.c +index 3cb5f02..48b2873 100644 +--- a/src/xt.c ++++ b/src/xt.c +@@ -124,7 +124,7 @@ void xt_stmt_xlate(const struct stmt *stmt, struct output_ctx *octx) + + void xt_stmt_destroy(struct stmt *stmt) + { +- xfree(stmt->xt.name); ++ free_const(stmt->xt.name); + xfree(stmt->xt.info); + } + diff --git a/0026-src-remove-xfree-and-use-plain-free.patch b/0026-src-remove-xfree-and-use-plain-free.patch new file mode 100644 index 0000000..a02dfa8 --- /dev/null +++ b/0026-src-remove-xfree-and-use-plain-free.patch @@ -0,0 +1,635 @@ +From 91824e5e97716d8d433ea8c064bd7eb701e006f6 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:40 +0200 +Subject: [PATCH] src: remove xfree() and use plain free() + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit d7af8ab26d62e602b73e1017183f899923d8d5ae + +commit d7af8ab26d62e602b73e1017183f899923d8d5ae +Author: Thomas Haller +Date: Tue Oct 24 11:57:10 2023 +0200 + + src: remove xfree() and use plain free() + + xmalloc() (and similar x-functions) are used for allocation. They wrap + malloc()/realloc() but will abort the program on ENOMEM. + + The meaning of xmalloc() is that it wraps malloc() but aborts on + failure. I don't think x-functions should have the notion, that this + were potentially a different memory allocator that must be paired + with a particular xfree(). + + Even if the original intent was that the allocator is abstracted (and + possibly not backed by standard malloc()/free()), then that doesn't seem + a good idea. Nowadays libc allocators are pretty good, and we would need + a very special use cases to switch to something else. In other words, + it will never happen that xmalloc() is not backed by malloc(). + + Also there were a few places, where a xmalloc() was already "wrongly" + paired with free() (for example, iface_cache_release(), exit_cookie(), + nft_run_cmd_from_buffer()). + + Or note how pid2name() returns an allocated string from fscanf(), which + needs to be freed with free() (and not xfree()). This requirement + bubbles up the callers portid2name() and name_by_portid(). This case was + actually handled correctly and the buffer was freed with free(). But it + shows that mixing different allocators is cumbersome to get right. Of + course, we don't actually have different allocators and whether to use + free() or xfree() makes no different. The point is that xfree() serves + no actual purpose except raising irrelevant questions about whether + x-functions are correctly paired with xfree(). + + Note that xfree() also used to accept const pointers. It is bad to + unconditionally for all deallocations. Instead prefer to use plain + free(). To free a const pointer use free_const() which obviously wraps + free, as indicated by the name. + + Signed-off-by: Thomas Haller + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + include/utils.h | 1 - + src/cache.c | 6 +++--- + src/datatype.c | 4 ++-- + src/erec.c | 6 +++--- + src/evaluate.c | 4 ++-- + src/expression.c | 2 +- + src/json.c | 2 +- + src/libnftables.c | 12 ++++++------ + src/meta.c | 4 ++-- + src/misspell.c | 2 +- + src/mnl.c | 4 ++-- + src/netlink_linearize.c | 4 ++-- + src/optimize.c | 10 +++++----- + src/parser_bison.y | 14 +++++++------- + src/rule.c | 32 ++++++++++++++++---------------- + src/scanner.l | 2 +- + src/segtree.c | 4 ++-- + src/statement.c | 2 +- + src/utils.c | 5 ----- + src/xt.c | 8 ++++---- + 20 files changed, 61 insertions(+), 67 deletions(-) + +diff --git a/include/utils.h b/include/utils.h +index 36a28f8..e18fabe 100644 +--- a/include/utils.h ++++ b/include/utils.h +@@ -142,7 +142,6 @@ extern void __memory_allocation_error(const char *filename, uint32_t line) __nor + #define memory_allocation_error() \ + __memory_allocation_error(__FILE__, __LINE__); + +-extern void xfree(const void *ptr); + extern void *xmalloc(size_t size); + extern void *xmalloc_array(size_t nmemb, size_t size); + extern void *xrealloc(void *ptr, size_t size); +diff --git a/src/cache.c b/src/cache.c +index 0ac0f7c..a3cd795 100644 +--- a/src/cache.c ++++ b/src/cache.c +@@ -126,9 +126,9 @@ void nft_cache_filter_fini(struct nft_cache_filter *filter) + struct nft_filter_obj *obj, *next; + + list_for_each_entry_safe(obj, next, &filter->obj[i].head, list) +- xfree(obj); ++ free(obj); + } +- xfree(filter); ++ free(filter); + } + + static void cache_filter_add(struct nft_cache_filter *filter, +@@ -1275,7 +1275,7 @@ void cache_init(struct cache *cache) + + void cache_free(struct cache *cache) + { +- xfree(cache->ht); ++ free(cache->ht); + } + + void cache_add(struct cache_item *item, struct cache *cache, uint32_t hash) +diff --git a/src/datatype.c b/src/datatype.c +index 9353ff2..a92f41d 100644 +--- a/src/datatype.c ++++ b/src/datatype.c +@@ -1298,7 +1298,7 @@ void datatype_free(const struct datatype *ptr) + + free_const(dtype->name); + free_const(dtype->desc); +- xfree(dtype); ++ free(dtype); + } + + const struct datatype *concat_type_alloc(uint32_t type) +@@ -1545,7 +1545,7 @@ static void cgroupv2_type_print(const struct expr *expr, + else + nft_print(octx, "%" PRIu64, id); + +- xfree(cgroup_path); ++ free(cgroup_path); + } + + static struct error_record *cgroupv2_type_parse(struct parse_ctx *ctx, +diff --git a/src/erec.c b/src/erec.c +index cd9f62b..fe66abb 100644 +--- a/src/erec.c ++++ b/src/erec.c +@@ -43,8 +43,8 @@ void erec_add_location(struct error_record *erec, const struct location *loc) + + void erec_destroy(struct error_record *erec) + { +- xfree(erec->msg); +- xfree(erec); ++ free(erec->msg); ++ free(erec); + } + + __attribute__((format(printf, 3, 0))) +@@ -203,7 +203,7 @@ void erec_print(struct output_ctx *octx, const struct error_record *erec, + } + pbuf[end] = '\0'; + fprintf(f, "%s", pbuf); +- xfree(pbuf); ++ free(pbuf); + } + fprintf(f, "\n"); + } +diff --git a/src/evaluate.c b/src/evaluate.c +index c41210b..27ecaa2 100644 +--- a/src/evaluate.c ++++ b/src/evaluate.c +@@ -3249,7 +3249,7 @@ static int stmt_reject_gen_dependency(struct eval_ctx *ctx, struct stmt *stmt, + */ + list_add(&nstmt->list, &ctx->rule->stmts); + out: +- xfree(payload); ++ free(payload); + return ret; + } + +@@ -5148,7 +5148,7 @@ static int ct_timeout_evaluate(struct eval_ctx *ctx, struct obj *obj) + ct->timeout[ts->timeout_index] = ts->timeout_value; + list_del(&ts->head); + free_const(ts->timeout_str); +- xfree(ts); ++ free(ts); + } + + return 0; +diff --git a/src/expression.c b/src/expression.c +index 0b4a537..dde48b6 100644 +--- a/src/expression.c ++++ b/src/expression.c +@@ -94,7 +94,7 @@ void expr_free(struct expr *expr) + */ + if (expr->etype != EXPR_INVALID) + expr_destroy(expr); +- xfree(expr); ++ free(expr); + } + + void expr_print(const struct expr *expr, struct output_ctx *octx) +diff --git a/src/json.c b/src/json.c +index dad93e5..98fa026 100644 +--- a/src/json.c ++++ b/src/json.c +@@ -83,7 +83,7 @@ static json_t *set_dtype_json(const struct expr *key) + json_array_append_new(root, jtok); + tok = strtok_r(NULL, " .", &tok_safe); + } +- xfree(namedup); ++ free(namedup); + return root; + } + +diff --git a/src/libnftables.c b/src/libnftables.c +index 866b5c6..ec90200 100644 +--- a/src/libnftables.c ++++ b/src/libnftables.c +@@ -158,7 +158,7 @@ void nft_ctx_clear_vars(struct nft_ctx *ctx) + free_const(ctx->vars[i].value); + } + ctx->num_vars = 0; +- xfree(ctx->vars); ++ free(ctx->vars); + } + + EXPORT_SYMBOL(nft_ctx_add_include_path); +@@ -182,9 +182,9 @@ EXPORT_SYMBOL(nft_ctx_clear_include_paths); + void nft_ctx_clear_include_paths(struct nft_ctx *ctx) + { + while (ctx->num_include_paths) +- xfree(ctx->include_paths[--ctx->num_include_paths]); ++ free(ctx->include_paths[--ctx->num_include_paths]); + +- xfree(ctx->include_paths); ++ free(ctx->include_paths); + ctx->include_paths = NULL; + } + +@@ -343,9 +343,9 @@ void nft_ctx_free(struct nft_ctx *ctx) + nft_ctx_clear_vars(ctx); + nft_ctx_clear_include_paths(ctx); + scope_free(ctx->top_scope); +- xfree(ctx->state); ++ free(ctx->state); + nft_exit(ctx); +- xfree(ctx); ++ free(ctx); + } + + EXPORT_SYMBOL(nft_ctx_set_output); +@@ -745,7 +745,7 @@ err: + if (indesc->name) + free_const(indesc->name); + +- xfree(indesc); ++ free(indesc); + } + } + free_const(nft->vars_ctx.buf); +diff --git a/src/meta.c b/src/meta.c +index 1f1e33c..4e55e00 100644 +--- a/src/meta.c ++++ b/src/meta.c +@@ -99,13 +99,13 @@ static struct error_record *tchandle_type_parse(struct parse_ctx *ctx, + handle = strtoull(sym->identifier, NULL, 0); + } + out: +- xfree(str); ++ free(str); + *res = constant_expr_alloc(&sym->location, sym->dtype, + BYTEORDER_HOST_ENDIAN, + sizeof(handle) * BITS_PER_BYTE, &handle); + return NULL; + err: +- xfree(str); ++ free(str); + return error(&sym->location, "Could not parse %s", sym->dtype->desc); + } + +diff --git a/src/misspell.c b/src/misspell.c +index c1e58a0..f5354fa 100644 +--- a/src/misspell.c ++++ b/src/misspell.c +@@ -72,7 +72,7 @@ static unsigned int string_distance(const char *a, const char *b) + + ret = DISTANCE(len_a, len_b); + +- xfree(distance); ++ free(distance); + + return ret; + } +diff --git a/src/mnl.c b/src/mnl.c +index 0158924..9e4bfcd 100644 +--- a/src/mnl.c ++++ b/src/mnl.c +@@ -242,7 +242,7 @@ static void mnl_err_list_node_add(struct list_head *err_list, int error, + void mnl_err_list_free(struct mnl_err *err) + { + list_del(&err->head); +- xfree(err); ++ free(err); + } + + static void mnl_set_sndbuffer(struct netlink_ctx *ctx) +@@ -2179,7 +2179,7 @@ static void basehook_free(struct basehook *b) + free_const(b->hookfn); + free_const(b->chain); + free_const(b->table); +- xfree(b); ++ free(b); + } + + static void basehook_list_add_tail(struct basehook *b, struct list_head *head) +diff --git a/src/netlink_linearize.c b/src/netlink_linearize.c +index 0c62341..df395ba 100644 +--- a/src/netlink_linearize.c ++++ b/src/netlink_linearize.c +@@ -1743,9 +1743,9 @@ void netlink_linearize_fini(struct netlink_linearize_ctx *lctx) + + for (i = 0; i < NFT_EXPR_LOC_HSIZE; i++) { + list_for_each_entry_safe(eloc, next, &lctx->expr_loc_htable[i], hlist) +- xfree(eloc); ++ free(eloc); + } +- xfree(lctx->expr_loc_htable); ++ free(lctx->expr_loc_htable); + } + + void netlink_linearize_rule(struct netlink_ctx *ctx, +diff --git a/src/optimize.c b/src/optimize.c +index 9ae9283..b90dd99 100644 +--- a/src/optimize.c ++++ b/src/optimize.c +@@ -1347,16 +1347,16 @@ static int chain_optimize(struct nft_ctx *nft, struct list_head *rules) + } + ret = 0; + for (i = 0; i < ctx->num_rules; i++) +- xfree(ctx->stmt_matrix[i]); ++ free(ctx->stmt_matrix[i]); + +- xfree(ctx->stmt_matrix); +- xfree(merge); ++ free(ctx->stmt_matrix); ++ free(merge); + err: + for (i = 0; i < ctx->num_stmts; i++) + stmt_free(ctx->stmt[i]); + +- xfree(ctx->rule); +- xfree(ctx); ++ free(ctx->rule); ++ free(ctx); + + return ret; + } +diff --git a/src/parser_bison.y b/src/parser_bison.y +index b485a48..751b3e1 100644 +--- a/src/parser_bison.y ++++ b/src/parser_bison.y +@@ -712,7 +712,7 @@ int nft_lex(void *, void *, void *); + %destructor { free_const($$); } extended_prio_name quota_unit basehook_device_name + + %type dev_spec +-%destructor { xfree($$); } dev_spec ++%destructor { free($$); } dev_spec + + %type
table_block_alloc table_block + %destructor { close_scope(state); table_free($$); } table_block_alloc +@@ -741,7 +741,7 @@ int nft_lex(void *, void *, void *); + %destructor { obj_free($$); } obj_block_alloc + + %type stmt_list stateful_stmt_list set_elem_stmt_list +-%destructor { stmt_list_free($$); xfree($$); } stmt_list stateful_stmt_list set_elem_stmt_list ++%destructor { stmt_list_free($$); free($$); } stmt_list stateful_stmt_list set_elem_stmt_list + %type stmt match_stmt verdict_stmt set_elem_stmt + %destructor { stmt_free($$); } stmt match_stmt verdict_stmt set_elem_stmt + %type counter_stmt counter_stmt_alloc stateful_stmt last_stmt +@@ -967,7 +967,7 @@ int nft_lex(void *, void *, void *); + %type ct_l4protoname ct_obj_type ct_cmd_type + + %type timeout_states timeout_state +-%destructor { xfree($$); } timeout_states timeout_state ++%destructor { free($$); } timeout_states timeout_state + + %type xfrm_state_key xfrm_state_proto_key xfrm_dir xfrm_spnum + %type xfrm_expr +@@ -3030,7 +3030,7 @@ rule_alloc : stmt_list + list_for_each_entry(i, $1, list) + $$->num_stmts++; + list_splice_tail($1, &$$->stmts); +- xfree($1); ++ free($1); + } + ; + +@@ -4537,7 +4537,7 @@ set_elem_expr : set_elem_expr_alloc + { + $$ = $1; + list_splice_tail($3, &$$->stmt_list); +- xfree($3); ++ free($3); + } + ; + +@@ -4549,7 +4549,7 @@ set_elem_expr_alloc : set_elem_key_expr set_elem_stmt_list + { + $$ = set_elem_expr_alloc(&@1, $1); + list_splice_tail($2, &$$->stmt_list); +- xfree($2); ++ free($2); + } + | set_elem_key_expr + { +@@ -4861,7 +4861,7 @@ ct_timeout_config : PROTOCOL ct_l4protoname stmt_separator + + ct = &$0->ct_timeout; + list_splice_tail($4, &ct->timeout_list); +- xfree($4); ++ free($4); + } + | L3PROTOCOL family_spec_explicit stmt_separator + { +diff --git a/src/rule.c b/src/rule.c +index 76cd58c..633fae7 100644 +--- a/src/rule.c ++++ b/src/rule.c +@@ -200,7 +200,7 @@ void set_free(struct set *set) + stmt_free(stmt); + expr_free(set->key); + expr_free(set->data); +- xfree(set); ++ free(set); + } + + struct set *set_lookup_fuzzy(const char *set_name, +@@ -480,7 +480,7 @@ void rule_free(struct rule *rule) + stmt_list_free(&rule->stmts); + handle_free(&rule->handle); + free_const(rule->comment); +- xfree(rule); ++ free(rule); + } + + void rule_print(const struct rule *rule, struct output_ctx *octx) +@@ -559,14 +559,14 @@ void scope_release(const struct scope *scope) + list_del(&sym->list); + free_const(sym->identifier); + expr_free(sym->expr); +- xfree(sym); ++ free(sym); + } + } + + void scope_free(struct scope *scope) + { + scope_release(scope); +- xfree(scope); ++ free(scope); + } + + void symbol_bind(struct scope *scope, const char *identifier, struct expr *expr) +@@ -599,7 +599,7 @@ static void symbol_put(struct symbol *sym) + if (--sym->refcnt == 0) { + free_const(sym->identifier); + expr_free(sym->expr); +- xfree(sym); ++ free(sym); + } + } + +@@ -734,11 +734,11 @@ void chain_free(struct chain *chain) + expr_free(chain->dev_expr); + for (i = 0; i < chain->dev_array_len; i++) + free_const(chain->dev_array[i]); +- xfree(chain->dev_array); ++ free(chain->dev_array); + expr_free(chain->priority.expr); + expr_free(chain->policy); + free_const(chain->comment); +- xfree(chain); ++ free(chain); + } + + struct chain *chain_binding_lookup(const struct table *table, +@@ -1181,7 +1181,7 @@ void table_free(struct table *table) + cache_free(&table->set_cache); + cache_free(&table->obj_cache); + cache_free(&table->ft_cache); +- xfree(table); ++ free(table); + } + + struct table *table_get(struct table *table) +@@ -1342,7 +1342,7 @@ struct markup *markup_alloc(uint32_t format) + + void markup_free(struct markup *m) + { +- xfree(m); ++ free(m); + } + + struct monitor *monitor_alloc(uint32_t format, uint32_t type, const char *event) +@@ -1361,7 +1361,7 @@ struct monitor *monitor_alloc(uint32_t format, uint32_t type, const char *event) + void monitor_free(struct monitor *m) + { + free_const(m->event); +- xfree(m); ++ free(m); + } + + void cmd_free(struct cmd *cmd) +@@ -1415,9 +1415,9 @@ void cmd_free(struct cmd *cmd) + BUG("invalid command object type %u\n", cmd->obj); + } + } +- xfree(cmd->attr); ++ free(cmd->attr); + free_const(cmd->arg); +- xfree(cmd); ++ free(cmd); + } + + #include +@@ -1662,10 +1662,10 @@ void obj_free(struct obj *obj) + list_for_each_entry_safe(ts, next, &obj->ct_timeout.timeout_list, head) { + list_del(&ts->head); + free_const(ts->timeout_str); +- xfree(ts); ++ free(ts); + } + } +- xfree(obj); ++ free(obj); + } + + struct obj *obj_lookup_fuzzy(const char *obj_name, +@@ -2075,9 +2075,9 @@ void flowtable_free(struct flowtable *flowtable) + if (flowtable->dev_array != NULL) { + for (i = 0; i < flowtable->dev_array_len; i++) + free_const(flowtable->dev_array[i]); +- xfree(flowtable->dev_array); ++ free(flowtable->dev_array); + } +- xfree(flowtable); ++ free(flowtable); + } + + static void flowtable_print_declaration(const struct flowtable *flowtable, +diff --git a/src/scanner.l b/src/scanner.l +index 93a31f2..00a0948 100644 +--- a/src/scanner.l ++++ b/src/scanner.l +@@ -1284,7 +1284,7 @@ void scanner_destroy(struct nft_ctx *nft) + struct parser_state *state = yyget_extra(nft->scanner); + + input_descriptor_list_destroy(state); +- xfree(state->startcond_active); ++ free(state->startcond_active); + + yylex_destroy(nft->scanner); + } +diff --git a/src/segtree.c b/src/segtree.c +index c4029f9..7d246de 100644 +--- a/src/segtree.c ++++ b/src/segtree.c +@@ -656,6 +656,6 @@ out: + if (catchall) + compound_expr_add(set, catchall); + +- xfree(ranges); +- xfree(elements); ++ free(ranges); ++ free(elements); + } +diff --git a/src/statement.c b/src/statement.c +index 994b522..ab144d6 100644 +--- a/src/statement.c ++++ b/src/statement.c +@@ -51,7 +51,7 @@ void stmt_free(struct stmt *stmt) + return; + if (stmt->ops->destroy) + stmt->ops->destroy(stmt); +- xfree(stmt); ++ free(stmt); + } + + void stmt_list_free(struct list_head *list) +diff --git a/src/utils.c b/src/utils.c +index e6ad8b8..2aa1eb4 100644 +--- a/src/utils.c ++++ b/src/utils.c +@@ -24,11 +24,6 @@ void __noreturn __memory_allocation_error(const char *filename, uint32_t line) + exit(NFT_EXIT_NOMEM); + } + +-void xfree(const void *ptr) +-{ +- free((void *)ptr); +-} +- + void *xmalloc(size_t size) + { + void *ptr; +diff --git a/src/xt.c b/src/xt.c +index 48b2873..f7bee21 100644 +--- a/src/xt.c ++++ b/src/xt.c +@@ -78,7 +78,7 @@ void xt_stmt_xlate(const struct stmt *stmt, struct output_ctx *octx) + + rc = mt->xlate(xl, ¶ms); + } +- xfree(m); ++ free(m); + break; + case NFT_XT_WATCHER: + case NFT_XT_TARGET: +@@ -108,14 +108,14 @@ void xt_stmt_xlate(const struct stmt *stmt, struct output_ctx *octx) + + rc = tg->xlate(xl, ¶ms); + } +- xfree(t); ++ free(t); + break; + } + + if (rc == 1) + nft_print(octx, "%s", xt_xlate_get(xl)); + xt_xlate_free(xl); +- xfree(entry); ++ free(entry); + #endif + if (!rc) + nft_print(octx, "xt %s \"%s\"", +@@ -125,7 +125,7 @@ void xt_stmt_xlate(const struct stmt *stmt, struct output_ctx *octx) + void xt_stmt_destroy(struct stmt *stmt) + { + free_const(stmt->xt.name); +- xfree(stmt->xt.info); ++ free(stmt->xt.info); + } + + #ifdef HAVE_LIBXTABLES diff --git a/0027-netlink-fix-buffer-size-for-user-data-in-netlink_del.patch b/0027-netlink-fix-buffer-size-for-user-data-in-netlink_del.patch new file mode 100644 index 0000000..6509016 --- /dev/null +++ b/0027-netlink-fix-buffer-size-for-user-data-in-netlink_del.patch @@ -0,0 +1,41 @@ +From 643ad1a2a68d58fd7fbbaefd1af83c60f229ef0b Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:40 +0200 +Subject: [PATCH] netlink: fix buffer size for user data in + netlink_delinearize_chain() + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 505a6794422238f9f1d590fe8c1ee3ea7fd46579 + +commit 505a6794422238f9f1d590fe8c1ee3ea7fd46579 +Author: Thomas Haller +Date: Wed Nov 8 19:22:20 2023 +0100 + + netlink: fix buffer size for user data in netlink_delinearize_chain() + + The correct define is NFTNL_UDATA_CHAIN_MAX and not NFTNL_UDATA_OBJ_MAX. + In current libnftnl, they both are defined as 1, so (with current libnftnl) + there is no difference. + + Fixes: 702ac2b72c0e ("src: add comment support for chains") + Signed-off-by: Thomas Haller + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/netlink.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/netlink.c b/src/netlink.c +index 04bba59..a366758 100644 +--- a/src/netlink.c ++++ b/src/netlink.c +@@ -617,7 +617,7 @@ static int qsort_device_cmp(const void *a, const void *b) + struct chain *netlink_delinearize_chain(struct netlink_ctx *ctx, + const struct nftnl_chain *nlc) + { +- const struct nftnl_udata *ud[NFTNL_UDATA_OBJ_MAX + 1] = {}; ++ const struct nftnl_udata *ud[NFTNL_UDATA_CHAIN_MAX + 1] = {}; + int priority, policy, len = 0, i; + const char * const *dev_array; + struct chain *chain; diff --git a/0028-tests-shell-split-merge-nat-optimization-in-two-test.patch b/0028-tests-shell-split-merge-nat-optimization-in-two-test.patch new file mode 100644 index 0000000..c8fb954 --- /dev/null +++ b/0028-tests-shell-split-merge-nat-optimization-in-two-test.patch @@ -0,0 +1,110 @@ +From 800e92dac205e2f7684414528e3605abb93861f7 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:40 +0200 +Subject: [PATCH] tests: shell: split merge nat optimization in two tests + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 0f89958eefd5318f594a653b78b47cefec9e87e9 + +commit 0f89958eefd5318f594a653b78b47cefec9e87e9 +Author: Pablo Neira Ayuso +Date: Tue Nov 7 13:03:56 2023 +0100 + + tests: shell: split merge nat optimization in two tests + + One without pipapo support and another with not to harm existing + coverage. + + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + .../optimizations/dumps/merge_nat.nft | 8 -------- + .../optimizations/dumps/merge_nat_concat.nft | 8 ++++++++ + tests/shell/testcases/optimizations/merge_nat | 13 ------------- + .../testcases/optimizations/merge_nat_concat | 18 ++++++++++++++++++ + 4 files changed, 26 insertions(+), 21 deletions(-) + create mode 100644 tests/shell/testcases/optimizations/dumps/merge_nat_concat.nft + create mode 100755 tests/shell/testcases/optimizations/merge_nat_concat + +diff --git a/tests/shell/testcases/optimizations/dumps/merge_nat.nft b/tests/shell/testcases/optimizations/dumps/merge_nat.nft +index 48d18a6..61feb27 100644 +--- a/tests/shell/testcases/optimizations/dumps/merge_nat.nft ++++ b/tests/shell/testcases/optimizations/dumps/merge_nat.nft +@@ -11,14 +11,6 @@ table ip test2 { + ip saddr { 10.141.11.0/24, 10.141.13.0/24 } masquerade + } + } +-table ip test3 { +- chain y { +- oif "lo" accept +- snat to ip saddr . tcp sport map { 1.1.1.1 . 1024-65535 : 3.3.3.3, 2.2.2.2 . 1024-65535 : 4.4.4.4 } +- oifname "enp2s0" snat ip to ip saddr map { 10.1.1.0/24 : 72.2.3.66-72.2.3.78 } +- tcp dport { 8888, 9999 } redirect +- } +-} + table ip test4 { + chain y { + oif "lo" accept +diff --git a/tests/shell/testcases/optimizations/dumps/merge_nat_concat.nft b/tests/shell/testcases/optimizations/dumps/merge_nat_concat.nft +new file mode 100644 +index 0000000..0faddfd +--- /dev/null ++++ b/tests/shell/testcases/optimizations/dumps/merge_nat_concat.nft +@@ -0,0 +1,8 @@ ++table ip test3 { ++ chain y { ++ oif "lo" accept ++ snat to ip saddr . tcp sport map { 1.1.1.1 . 1024-65535 : 3.3.3.3, 2.2.2.2 . 1024-65535 : 4.4.4.4 } ++ oifname "enp2s0" snat ip to ip saddr map { 10.1.1.0/24 : 72.2.3.66-72.2.3.78 } ++ tcp dport { 8888, 9999 } redirect ++ } ++} +diff --git a/tests/shell/testcases/optimizations/merge_nat b/tests/shell/testcases/optimizations/merge_nat +index 3a57d94..bfe9787 100755 +--- a/tests/shell/testcases/optimizations/merge_nat ++++ b/tests/shell/testcases/optimizations/merge_nat +@@ -24,19 +24,6 @@ RULESET="table ip test2 { + + $NFT -o -f - <<< $RULESET + +-RULESET="table ip test3 { +- chain y { +- oif lo accept +- ip saddr 1.1.1.1 tcp sport 1024-65535 snat to 3.3.3.3 +- ip saddr 2.2.2.2 tcp sport 1024-65535 snat to 4.4.4.4 +- oifname enp2s0 snat ip to ip saddr map { 10.1.1.0/24 : 72.2.3.66-72.2.3.78 } +- tcp dport 8888 redirect +- tcp dport 9999 redirect +- } +-}" +- +-$NFT -o -f - <<< $RULESET +- + RULESET="table ip test4 { + chain y { + oif lo accept +diff --git a/tests/shell/testcases/optimizations/merge_nat_concat b/tests/shell/testcases/optimizations/merge_nat_concat +new file mode 100755 +index 0000000..2e0a91a +--- /dev/null ++++ b/tests/shell/testcases/optimizations/merge_nat_concat +@@ -0,0 +1,18 @@ ++#!/bin/bash ++ ++# NFT_TEST_REQUIRES(NFT_TEST_HAVE_pipapo) ++ ++set -e ++ ++RULESET="table ip test3 { ++ chain y { ++ oif lo accept ++ ip saddr 1.1.1.1 tcp sport 1024-65535 snat to 3.3.3.3 ++ ip saddr 2.2.2.2 tcp sport 1024-65535 snat to 4.4.4.4 ++ oifname enp2s0 snat ip to ip saddr map { 10.1.1.0/24 : 72.2.3.66-72.2.3.78 } ++ tcp dport 8888 redirect ++ tcp dport 9999 redirect ++ } ++}" ++ ++$NFT -o -f - <<< $RULESET diff --git a/0029-tests-shell-split-set-NAT-interval-test.patch b/0029-tests-shell-split-set-NAT-interval-test.patch new file mode 100644 index 0000000..edfffea --- /dev/null +++ b/0029-tests-shell-split-set-NAT-interval-test.patch @@ -0,0 +1,123 @@ +From 20b2a43a27321b052fb9d626b13eb6de3668a9c8 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:40 +0200 +Subject: [PATCH] tests: shell: split set NAT interval test + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit b95bbe966bd2431fee7b19aa0902aba10f9be696 + +commit b95bbe966bd2431fee7b19aa0902aba10f9be696 +Author: Pablo Neira Ayuso +Date: Tue Nov 7 11:41:19 2023 +0100 + + tests: shell: split set NAT interval test + + Split test in two, one for interval sets and another with concatenation + + intervals, so at least intervals are tested in older kernels with no + pipapo support. + + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + .../testcases/sets/0067nat_concat_interval_0 | 17 ++--------------- + tests/shell/testcases/sets/0067nat_interval_0 | 18 ++++++++++++++++++ + .../sets/dumps/0067nat_concat_interval_0.nft | 7 ------- + .../sets/dumps/0067nat_interval_0.nft | 12 ++++++++++++ + 4 files changed, 32 insertions(+), 22 deletions(-) + create mode 100755 tests/shell/testcases/sets/0067nat_interval_0 + create mode 100644 tests/shell/testcases/sets/dumps/0067nat_interval_0.nft + +diff --git a/tests/shell/testcases/sets/0067nat_concat_interval_0 b/tests/shell/testcases/sets/0067nat_concat_interval_0 +index 55cc0d4..8162195 100755 +--- a/tests/shell/testcases/sets/0067nat_concat_interval_0 ++++ b/tests/shell/testcases/sets/0067nat_concat_interval_0 +@@ -1,21 +1,8 @@ + #!/bin/bash + +-set -e +- +-EXPECTED="table ip nat { +- map ipportmap { +- type ipv4_addr : interval ipv4_addr . inet_service +- flags interval +- elements = { 192.168.1.2 : 10.141.10.1-10.141.10.3 . 8888-8999 } +- } +- chain prerouting { +- type nat hook prerouting priority dstnat; policy accept; +- ip protocol tcp dnat ip to ip saddr map @ipportmap +- } +-}" ++# NFT_TEST_REQUIRES(NFT_TEST_HAVE_pipapo) + +-$NFT -f - <<< $EXPECTED +-$NFT add element ip nat ipportmap { 192.168.2.0/24 : 10.141.11.5-10.141.11.20 . 8888-8999 } ++set -e + + EXPECTED="table ip nat { + map ipportmap2 { +diff --git a/tests/shell/testcases/sets/0067nat_interval_0 b/tests/shell/testcases/sets/0067nat_interval_0 +new file mode 100755 +index 0000000..c90203d +--- /dev/null ++++ b/tests/shell/testcases/sets/0067nat_interval_0 +@@ -0,0 +1,18 @@ ++#!/bin/bash ++ ++set -e ++ ++EXPECTED="table ip nat { ++ map ipportmap { ++ type ipv4_addr : interval ipv4_addr . inet_service ++ flags interval ++ elements = { 192.168.1.2 : 10.141.10.1-10.141.10.3 . 8888-8999 } ++ } ++ chain prerouting { ++ type nat hook prerouting priority dstnat; policy accept; ++ ip protocol tcp dnat ip to ip saddr map @ipportmap ++ } ++}" ++ ++$NFT -f - <<< $EXPECTED ++$NFT add element ip nat ipportmap { 192.168.2.0/24 : 10.141.11.5-10.141.11.20 . 8888-8999 } +diff --git a/tests/shell/testcases/sets/dumps/0067nat_concat_interval_0.nft b/tests/shell/testcases/sets/dumps/0067nat_concat_interval_0.nft +index 0215691..9ac3774 100644 +--- a/tests/shell/testcases/sets/dumps/0067nat_concat_interval_0.nft ++++ b/tests/shell/testcases/sets/dumps/0067nat_concat_interval_0.nft +@@ -1,10 +1,4 @@ + table ip nat { +- map ipportmap { +- type ipv4_addr : interval ipv4_addr . inet_service +- flags interval +- elements = { 192.168.1.2 : 10.141.10.1-10.141.10.3 . 8888-8999, 192.168.2.0/24 : 10.141.11.5-10.141.11.20 . 8888-8999 } +- } +- + map ipportmap2 { + type ipv4_addr . ipv4_addr : interval ipv4_addr . inet_service + flags interval +@@ -33,7 +27,6 @@ table ip nat { + + chain prerouting { + type nat hook prerouting priority dstnat; policy accept; +- ip protocol tcp dnat ip to ip saddr map @ipportmap + ip protocol tcp dnat ip to ip saddr . ip daddr map @ipportmap2 + meta l4proto { tcp, udp } dnat ip to ip daddr . th dport map @fwdtoip_th + dnat ip to iifname . ip saddr map @ipportmap4 +diff --git a/tests/shell/testcases/sets/dumps/0067nat_interval_0.nft b/tests/shell/testcases/sets/dumps/0067nat_interval_0.nft +new file mode 100644 +index 0000000..b6d07fc +--- /dev/null ++++ b/tests/shell/testcases/sets/dumps/0067nat_interval_0.nft +@@ -0,0 +1,12 @@ ++table ip nat { ++ map ipportmap { ++ type ipv4_addr : interval ipv4_addr . inet_service ++ flags interval ++ elements = { 192.168.1.2 : 10.141.10.1-10.141.10.3 . 8888-8999, 192.168.2.0/24 : 10.141.11.5-10.141.11.20 . 8888-8999 } ++ } ++ ++ chain prerouting { ++ type nat hook prerouting priority dstnat; policy accept; ++ ip protocol tcp dnat ip to ip saddr map @ipportmap ++ } ++} diff --git a/0030-src-expand-create-commands.patch b/0030-src-expand-create-commands.patch new file mode 100644 index 0000000..611d4d1 --- /dev/null +++ b/0030-src-expand-create-commands.patch @@ -0,0 +1,132 @@ +From 6d277e247b5022f59f733acbbe74f609937d5b9d Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:40 +0200 +Subject: [PATCH] src: expand create commands + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 04a1ddc2012964c0a00350973328f5954887cedb + +commit 04a1ddc2012964c0a00350973328f5954887cedb +Author: Pablo Neira Ayuso +Date: Mon Nov 13 14:39:23 2023 +0100 + + src: expand create commands + + create commands also need to be expanded, otherwise elements are never + evaluated: + + # cat ruleset.nft + define ip-block-4 = { 1.1.1.1 } + create set netdev filter ip-block-4-test { + type ipv4_addr + flags interval + auto-merge + elements = $ip-block-4 + } + # nft -f ruleset.nft + BUG: unhandled expression type 0 + nft: src/intervals.c:211: interval_expr_key: Assertion `0' failed. + Aborted + + Same applies to chains in the form of: + + create chain x y { + counter + } + + which is also accepted by the parser. + + Update tests/shell to improve coverage for these use cases. + + Fixes: 56c90a2dd2eb ("evaluate: expand sets and maps before evaluation") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/libnftables.c | 3 ++- + tests/shell/testcases/include/0020include_chain_0 | 7 +++++++ + .../testcases/include/dumps/0020include_chain_0.nft | 5 +++++ + tests/shell/testcases/sets/0049set_define_0 | 12 ++++++++++++ + .../shell/testcases/sets/dumps/0049set_define_0.nft | 7 +++++++ + 5 files changed, 33 insertions(+), 1 deletion(-) + +diff --git a/src/libnftables.c b/src/libnftables.c +index ec90200..0dee1ba 100644 +--- a/src/libnftables.c ++++ b/src/libnftables.c +@@ -532,7 +532,8 @@ static int nft_evaluate(struct nft_ctx *nft, struct list_head *msgs, + collapsed = true; + + list_for_each_entry(cmd, cmds, list) { +- if (cmd->op != CMD_ADD) ++ if (cmd->op != CMD_ADD && ++ cmd->op != CMD_CREATE) + continue; + + nft_cmd_expand(cmd); +diff --git a/tests/shell/testcases/include/0020include_chain_0 b/tests/shell/testcases/include/0020include_chain_0 +index 8f78e8c..49b6f76 100755 +--- a/tests/shell/testcases/include/0020include_chain_0 ++++ b/tests/shell/testcases/include/0020include_chain_0 +@@ -20,4 +20,11 @@ RULESET2="chain inet filter input2 { + + echo "$RULESET2" > $tmpfile1 + ++RULESET3="create chain inet filter output2 { ++ type filter hook output priority filter; policy accept; ++ ip daddr 1.2.3.4 tcp dport { 22, 443, 123 } drop ++}" ++ ++echo "$RULESET3" >> $tmpfile1 ++ + $NFT -o -f - <<< $RULESET +diff --git a/tests/shell/testcases/include/dumps/0020include_chain_0.nft b/tests/shell/testcases/include/dumps/0020include_chain_0.nft +index 3ad6db1..bf596ff 100644 +--- a/tests/shell/testcases/include/dumps/0020include_chain_0.nft ++++ b/tests/shell/testcases/include/dumps/0020include_chain_0.nft +@@ -3,4 +3,9 @@ table inet filter { + type filter hook input priority filter; policy accept; + ip saddr 1.2.3.4 tcp dport { 22, 123, 443 } drop + } ++ ++ chain output2 { ++ type filter hook output priority filter; policy accept; ++ ip daddr 1.2.3.4 tcp dport { 22, 123, 443 } drop ++ } + } +diff --git a/tests/shell/testcases/sets/0049set_define_0 b/tests/shell/testcases/sets/0049set_define_0 +index 1d512f7..756afdc 100755 +--- a/tests/shell/testcases/sets/0049set_define_0 ++++ b/tests/shell/testcases/sets/0049set_define_0 +@@ -14,3 +14,15 @@ table inet filter { + " + + $NFT -f - <<< "$EXPECTED" ++ ++EXPECTED="define ip-block-4 = { 1.1.1.1 } ++ ++ create set inet filter ip-block-4-test { ++ type ipv4_addr ++ flags interval ++ auto-merge ++ elements = \$ip-block-4 ++ } ++" ++ ++$NFT -f - <<< "$EXPECTED" +diff --git a/tests/shell/testcases/sets/dumps/0049set_define_0.nft b/tests/shell/testcases/sets/dumps/0049set_define_0.nft +index 998b387..d654420 100644 +--- a/tests/shell/testcases/sets/dumps/0049set_define_0.nft ++++ b/tests/shell/testcases/sets/dumps/0049set_define_0.nft +@@ -1,4 +1,11 @@ + table inet filter { ++ set ip-block-4-test { ++ type ipv4_addr ++ flags interval ++ auto-merge ++ elements = { 1.1.1.1 } ++ } ++ + chain input { + type filter hook input priority filter; policy drop; + tcp dport { 22, 80, 443 } ct state new counter packets 0 bytes 0 accept diff --git a/0031-json-fix-use-after-free-in-table_flags_json.patch b/0031-json-fix-use-after-free-in-table_flags_json.patch new file mode 100644 index 0000000..079dcb1 --- /dev/null +++ b/0031-json-fix-use-after-free-in-table_flags_json.patch @@ -0,0 +1,57 @@ +From d5019748ad5b02980c7ff7349d6e55245c831a7c Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:40 +0200 +Subject: [PATCH] json: fix use after free in table_flags_json() + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit b04512cf30de1ba6657facba5ebe2321e17c2727 + +commit b04512cf30de1ba6657facba5ebe2321e17c2727 +Author: Thomas Haller +Date: Tue Nov 14 16:29:25 2023 +0100 + + json: fix use after free in table_flags_json() + + Add `$NFT -j list ruleset` to the end of "tests/shell/testcases/transactions/table_onoff". + Then valgrind will find this issue: + + $ make -j && ./tests/shell/run-tests.sh tests/shell/testcases/transactions/table_onoff -V + + Gives: + + ==286== Invalid read of size 4 + ==286== at 0x49B0261: do_dump (dump.c:211) + ==286== by 0x49B08B8: do_dump (dump.c:378) + ==286== by 0x49B08B8: do_dump (dump.c:378) + ==286== by 0x49B04F7: do_dump (dump.c:273) + ==286== by 0x49B08B8: do_dump (dump.c:378) + ==286== by 0x49B0E84: json_dump_callback (dump.c:465) + ==286== by 0x48AF22A: do_command_list_json (json.c:2016) + ==286== by 0x48732F1: do_command_list (rule.c:2335) + ==286== by 0x48737F5: do_command (rule.c:2605) + ==286== by 0x48A867D: nft_netlink (libnftables.c:42) + ==286== by 0x48A92B1: nft_run_cmd_from_buffer (libnftables.c:597) + ==286== by 0x402CBA: main (main.c:533) + + Fixes: e70354f53e9f ("libnftables: Implement JSON output support") + Signed-off-by: Thomas Haller + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/json.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/json.c b/src/json.c +index 98fa026..6809cd5 100644 +--- a/src/json.c ++++ b/src/json.c +@@ -497,7 +497,7 @@ static json_t *table_flags_json(const struct table *table) + json_decref(root); + return NULL; + case 1: +- json_unpack(root, "[o]", &tmp); ++ json_unpack(root, "[O]", &tmp); + json_decref(root); + root = tmp; + break; diff --git a/0032-tests-shell-restore-pipapo-and-chain-binding-coverag.patch b/0032-tests-shell-restore-pipapo-and-chain-binding-coverag.patch new file mode 100644 index 0000000..041f896 --- /dev/null +++ b/0032-tests-shell-restore-pipapo-and-chain-binding-coverag.patch @@ -0,0 +1,56 @@ +From dd1a258c4dd517dd4b89a8913fd4e1ae47dc0c34 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:40 +0200 +Subject: [PATCH] tests: shell: restore pipapo and chain binding coverage in + standalone 30s-stress + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 110f73023497720d4161263c03d8d7cc062af7ab + +commit 110f73023497720d4161263c03d8d7cc062af7ab +Author: Pablo Neira Ayuso +Date: Tue Nov 14 20:16:08 2023 +0100 + + tests: shell: restore pipapo and chain binding coverage in standalone 30s-stress + + Do not disable pipapo and chain binding coverage for standalone runs by + default. Instead, turn them on by default and allow users to disable them + through: + + # export NFT_TEST_HAVE_chain_binding=n; bash tests/shell/testcases/transactions/30s-stress 3600 + ... + running standalone with: + NFT_TEST_HAVE_chain_binding=n + NFT_TEST_HAVE_pipapo=y + + given feature detection is not available in this case, thus, user has to + provide an explicit hint on what this kernel supports. + + Fixes: c5b5b1044fdd ("tests/shell: add feature probing via "features/*.nft" files") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + tests/shell/testcases/transactions/30s-stress | 9 +++++++++ + 1 file changed, 9 insertions(+) + +diff --git a/tests/shell/testcases/transactions/30s-stress b/tests/shell/testcases/transactions/30s-stress +index 4c3c6a2..544eef1 100755 +--- a/tests/shell/testcases/transactions/30s-stress ++++ b/tests/shell/testcases/transactions/30s-stress +@@ -6,6 +6,15 @@ runtime=30 + + # allow stand-alone execution as well, e.g. '$0 3600' + if [ x"$1" != "x" ] ;then ++ if [ -z "${NFT_TEST_HAVE_chain_binding+x}" ]; then ++ NFT_TEST_HAVE_chain_binding=y ++ fi ++ if [ -z "${NFT_TEST_HAVE_pipapo+x}" ]; then ++ NFT_TEST_HAVE_pipapo=y ++ fi ++ echo "running standalone with:" ++ echo "NFT_TEST_HAVE_chain_binding="$NFT_TEST_HAVE_chain_binding ++ echo "NFT_TEST_HAVE_pipapo="$NFT_TEST_HAVE_pipapo + if [ $1 -ge 0 ]; then + runtime="$1" + else diff --git a/0033-tests-shell-skip-if-kernel-does-not-support-flowtabl.patch b/0033-tests-shell-skip-if-kernel-does-not-support-flowtabl.patch new file mode 100644 index 0000000..a7e27c0 --- /dev/null +++ b/0033-tests-shell-skip-if-kernel-does-not-support-flowtabl.patch @@ -0,0 +1,61 @@ +From 0a8f8baff7bdfe2001f63429de7d8317bf72f4fc Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:40 +0200 +Subject: [PATCH] tests: shell: skip if kernel does not support flowtable + counter + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 279654904d5186430588b475501b2ca0d3ab6517 + +commit 279654904d5186430588b475501b2ca0d3ab6517 +Author: Pablo Neira Ayuso +Date: Tue Nov 14 16:57:22 2023 +0100 + + tests: shell: skip if kernel does not support flowtable counter + + Check if kernel provides flowtable counter supports which is available + since 53c2b2899af7 ("netfilter: flowtable: add counter support"). + + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + tests/shell/features/flowtable_counter.sh | 16 ++++++++++++++++ + .../testcases/flowtable/0012flowtable_variable_0 | 2 ++ + 2 files changed, 18 insertions(+) + create mode 100755 tests/shell/features/flowtable_counter.sh + +diff --git a/tests/shell/features/flowtable_counter.sh b/tests/shell/features/flowtable_counter.sh +new file mode 100755 +index 0000000..a4c4c62 +--- /dev/null ++++ b/tests/shell/features/flowtable_counter.sh +@@ -0,0 +1,16 @@ ++#!/bin/bash ++ ++# 53c2b2899af7 ("netfilter: flowtable: add counter support") ++# v5.7-rc1~146^2~12^2~16 ++ ++EXPECTED="table ip filter2 { ++ flowtable main_ft2 { ++ hook ingress priority filter ++ devices = { lo } ++ counter ++ } ++}" ++ ++$NFT -f - <<< $EXPECTED ++ ++diff -u <($NFT list ruleset) - <<<"$EXPECTED" +diff --git a/tests/shell/testcases/flowtable/0012flowtable_variable_0 b/tests/shell/testcases/flowtable/0012flowtable_variable_0 +index 080059d..9c03820 100755 +--- a/tests/shell/testcases/flowtable/0012flowtable_variable_0 ++++ b/tests/shell/testcases/flowtable/0012flowtable_variable_0 +@@ -1,5 +1,7 @@ + #!/bin/bash + ++# NFT_TEST_REQUIRES(NFT_TEST_HAVE_flowtable_counter) ++ + set -e + + iface_cleanup() { diff --git a/0034-evaluate-fix-rule-replacement-with-anon-sets.patch b/0034-evaluate-fix-rule-replacement-with-anon-sets.patch new file mode 100644 index 0000000..a9f1707 --- /dev/null +++ b/0034-evaluate-fix-rule-replacement-with-anon-sets.patch @@ -0,0 +1,39 @@ +From ecd609b492cbc755529e75a6af8fd9c06b89135b Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:40 +0200 +Subject: [PATCH] evaluate: fix rule replacement with anon sets + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 256904b1ded6314974dddc75726149f7b19d33f4 + +commit 256904b1ded6314974dddc75726149f7b19d33f4 +Author: Florian Westphal +Date: Sun Nov 19 13:05:55 2023 +0100 + + evaluate: fix rule replacement with anon sets + + nft replace rule t c handle 3 'jhash ip protocol . ip saddr mod 170 vmap { 0-94 : goto wan1, 95-169 : goto wan2, 170-269 }"' + BUG: unhandled op 2 + nft: src/evaluate.c:1748: interval_set_eval: Assertion `0' failed. + + Fixes: 81e36530fcac ("src: replace interval segment tree overlap and automerge") + Reported-by: Tino Reichardt + Signed-off-by: Florian Westphal + +Signed-off-by: Phil Sutter +--- + src/evaluate.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/src/evaluate.c b/src/evaluate.c +index 27ecaa2..30190ac 100644 +--- a/src/evaluate.c ++++ b/src/evaluate.c +@@ -1723,6 +1723,7 @@ static int interval_set_eval(struct eval_ctx *ctx, struct set *set, + switch (ctx->cmd->op) { + case CMD_CREATE: + case CMD_ADD: ++ case CMD_REPLACE: + case CMD_INSERT: + if (set->automerge) { + ret = set_automerge(ctx->msgs, ctx->cmd, set, init, diff --git a/0035-tests-shell-adjust-add-after-delete-flowtable-for-ol.patch b/0035-tests-shell-adjust-add-after-delete-flowtable-for-ol.patch new file mode 100644 index 0000000..3bae697 --- /dev/null +++ b/0035-tests-shell-adjust-add-after-delete-flowtable-for-ol.patch @@ -0,0 +1,74 @@ +From 29629b90e74c3228434c4fc27e97c39c0761ed7e Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:40 +0200 +Subject: [PATCH] tests: shell: adjust add-after-delete flowtable for older + kernels + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 566182cd8259cfc22f9fd949e9ad3c15ed6a7945 + +commit 566182cd8259cfc22f9fd949e9ad3c15ed6a7945 +Author: Pablo Neira Ayuso +Date: Mon Nov 20 13:54:03 2023 +0100 + + tests: shell: adjust add-after-delete flowtable for older kernels + + Remove counter from flowtable, older kernels (<=5.4) do not support this + in testcases/flowtable/0013addafterdelete_0 so this bug is still + covered. + + Skip testcases/flowtable/0014addafterdelete_0 if flowtable counter + support is not available. + + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + tests/shell/testcases/flowtable/0013addafterdelete_0 | 2 -- + tests/shell/testcases/flowtable/0014addafterdelete_0 | 2 ++ + tests/shell/testcases/flowtable/dumps/0013addafterdelete_0.nft | 1 - + 3 files changed, 2 insertions(+), 3 deletions(-) + +diff --git a/tests/shell/testcases/flowtable/0013addafterdelete_0 b/tests/shell/testcases/flowtable/0013addafterdelete_0 +index b23ab97..56c9834 100755 +--- a/tests/shell/testcases/flowtable/0013addafterdelete_0 ++++ b/tests/shell/testcases/flowtable/0013addafterdelete_0 +@@ -7,7 +7,6 @@ RULESET='table inet filter { + flowtable f { + hook ingress priority filter - 1 + devices = { lo } +- counter + } + }' + +@@ -20,7 +19,6 @@ table inet filter { + flowtable f { + hook ingress priority filter - 1 + devices = { lo } +- counter + } + }' + +diff --git a/tests/shell/testcases/flowtable/0014addafterdelete_0 b/tests/shell/testcases/flowtable/0014addafterdelete_0 +index 6a24c4b..1ac6510 100755 +--- a/tests/shell/testcases/flowtable/0014addafterdelete_0 ++++ b/tests/shell/testcases/flowtable/0014addafterdelete_0 +@@ -1,5 +1,7 @@ + #!/bin/bash + ++# NFT_TEST_REQUIRES(NFT_TEST_HAVE_flowtable_counter) ++ + set -e + + RULESET='table inet filter { +diff --git a/tests/shell/testcases/flowtable/dumps/0013addafterdelete_0.nft b/tests/shell/testcases/flowtable/dumps/0013addafterdelete_0.nft +index 83fdd5d..67db7d0 100644 +--- a/tests/shell/testcases/flowtable/dumps/0013addafterdelete_0.nft ++++ b/tests/shell/testcases/flowtable/dumps/0013addafterdelete_0.nft +@@ -2,6 +2,5 @@ table inet filter { + flowtable f { + hook ingress priority filter - 1 + devices = { lo } +- counter + } + } diff --git a/0036-tests-shell-flush-connlimit-sets.patch b/0036-tests-shell-flush-connlimit-sets.patch new file mode 100644 index 0000000..543e415 --- /dev/null +++ b/0036-tests-shell-flush-connlimit-sets.patch @@ -0,0 +1,59 @@ +From 93fe20fda0915183e19c513515d3f98e2d2c2df3 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:40 +0200 +Subject: [PATCH] tests: shell: flush connlimit sets + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit bab3ede002da778e18fa5f30ac7b05c4de5e1de8 + +commit bab3ede002da778e18fa5f30ac7b05c4de5e1de8 +Author: Pablo Neira Ayuso +Date: Tue Nov 21 16:33:17 2023 +0100 + + tests: shell: flush connlimit sets + + Restored elements via set declaration are removed almost inmediately by + GC, this is causing spurious failures in test runs. + + Flush sets to ensure dump is always consistent. Still, cover that + restoring a set with connlimit elements do not. + + Fixes: 95d348d55a9e ("tests: shell: extend connlimit test") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + tests/shell/testcases/sets/0062set_connlimit_0 | 3 +++ + tests/shell/testcases/sets/dumps/0062set_connlimit_0.nft | 2 -- + 2 files changed, 3 insertions(+), 2 deletions(-) + +diff --git a/tests/shell/testcases/sets/0062set_connlimit_0 b/tests/shell/testcases/sets/0062set_connlimit_0 +index 48d589f..dab1da0 100755 +--- a/tests/shell/testcases/sets/0062set_connlimit_0 ++++ b/tests/shell/testcases/sets/0062set_connlimit_0 +@@ -24,3 +24,6 @@ RULESET="table ip x { + }" + + $NFT -f - <<< $RULESET ++ ++$NFT flush set ip x est-connlimit ++$NFT flush set ip x new-connlimit +diff --git a/tests/shell/testcases/sets/dumps/0062set_connlimit_0.nft b/tests/shell/testcases/sets/dumps/0062set_connlimit_0.nft +index 080d675..13bbb95 100644 +--- a/tests/shell/testcases/sets/dumps/0062set_connlimit_0.nft ++++ b/tests/shell/testcases/sets/dumps/0062set_connlimit_0.nft +@@ -3,7 +3,6 @@ table ip x { + type ipv4_addr + size 65535 + flags dynamic +- elements = { 84.245.120.167 ct count over 20 } + } + + set new-connlimit { +@@ -11,6 +10,5 @@ table ip x { + size 65535 + flags dynamic + ct count over 20 +- elements = { 84.245.120.167 ct count over 20 } + } + } diff --git a/0037-evaluate-bogus-error-when-adding-devices-to-flowtabl.patch b/0037-evaluate-bogus-error-when-adding-devices-to-flowtabl.patch new file mode 100644 index 0000000..ef87027 --- /dev/null +++ b/0037-evaluate-bogus-error-when-adding-devices-to-flowtabl.patch @@ -0,0 +1,77 @@ +From 65c523bdd547ea90488adebefcba672f0e676aca Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:40 +0200 +Subject: [PATCH] evaluate: bogus error when adding devices to flowtable + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 59d304f47a121afda867d792c709bc2c81946979 + +commit 59d304f47a121afda867d792c709bc2c81946979 +Author: Pablo Neira Ayuso +Date: Wed Nov 22 09:43:04 2023 +0100 + + evaluate: bogus error when adding devices to flowtable + + Bail out if flowtable declaration is missing and no devices are + specified. + + Otherwise, this reports a bogus error when adding new devices to an + existing flowtable. + + # nft -v + nftables v1.0.9 (Old Doc Yak #3) + # ip link add dummy1 type dummy + # ip link set dummy1 up + # nft 'create flowtable inet filter f1 { hook ingress priority 0; counter }' + # nft 'add flowtable inet filter f1 { devices = { dummy1 } ; }' + Error: missing hook and priority in flowtable declaration + add flowtable inet filter f1 { devices = { dummy1 } ; } + ^^^^^^^^^^^^^^^^^^^^^^^^ + + Fixes: 5ad475fce5a1 ("evaluate: bail out if new flowtable does not specify hook and priority") + Reported-by: Martin Gignac + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/evaluate.c | 2 +- + tests/shell/testcases/flowtable/0015destroy_0 | 8 ++++++++ + 2 files changed, 9 insertions(+), 1 deletion(-) + +diff --git a/src/evaluate.c b/src/evaluate.c +index 30190ac..d204d3e 100644 +--- a/src/evaluate.c ++++ b/src/evaluate.c +@@ -4862,7 +4862,7 @@ static int flowtable_evaluate(struct eval_ctx *ctx, struct flowtable *ft) + return table_not_found(ctx); + + if (!ft_cache_find(table, ft->handle.flowtable.name)) { +- if (!ft->hook.name) ++ if (!ft->hook.name && !ft->dev_expr) + return chain_error(ctx, ft, "missing hook and priority in flowtable declaration"); + + ft_cache_add(flowtable_get(ft), table); +diff --git a/tests/shell/testcases/flowtable/0015destroy_0 b/tests/shell/testcases/flowtable/0015destroy_0 +index d2a87da..cea3352 100755 +--- a/tests/shell/testcases/flowtable/0015destroy_0 ++++ b/tests/shell/testcases/flowtable/0015destroy_0 +@@ -2,6 +2,11 @@ + + # NFT_TEST_REQUIRES(NFT_TEST_HAVE_destroy) + ++trap "ip link del dummy1" EXIT ++ ++ip link add dummy1 type dummy ++ip link set dummy1 up ++ + $NFT add table t + + # pass for non-existent flowtable +@@ -9,4 +14,7 @@ $NFT destroy flowtable t f + + # successfully delete existing flowtable + $NFT add flowtable t f '{ hook ingress priority 10; devices = { lo }; }' ++ ++$NFT 'add flowtable t f { devices = { dummy1 } ; }' ++ + $NFT destroy flowtable t f diff --git a/0038-tests-shell-split-nat-inet-tests.patch b/0038-tests-shell-split-nat-inet-tests.patch new file mode 100644 index 0000000..56116f2 --- /dev/null +++ b/0038-tests-shell-split-nat-inet-tests.patch @@ -0,0 +1,115 @@ +From 2c05e5e6b0c3aedcefbe73dbab6ac7b4f1b13138 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:41 +0200 +Subject: [PATCH] tests: shell: split nat inet tests + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit b975de80bd1dbf0a04c6cb7a42e46c323c865de8 + +commit b975de80bd1dbf0a04c6cb7a42e46c323c865de8 +Author: Pablo Neira Ayuso +Date: Tue Nov 21 20:45:48 2023 +0100 + + tests: shell: split nat inet tests + + Detach nat inet from existing tests not to reduce test coverage. + + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + .../optimizations/dumps/merge_nat.nft | 11 ---------- + .../optimizations/dumps/merge_nat_inet.nft | 11 ++++++++++ + tests/shell/testcases/optimizations/merge_nat | 16 -------------- + .../testcases/optimizations/merge_nat_inet | 21 +++++++++++++++++++ + 4 files changed, 32 insertions(+), 27 deletions(-) + create mode 100644 tests/shell/testcases/optimizations/dumps/merge_nat_inet.nft + create mode 100755 tests/shell/testcases/optimizations/merge_nat_inet + +diff --git a/tests/shell/testcases/optimizations/dumps/merge_nat.nft b/tests/shell/testcases/optimizations/dumps/merge_nat.nft +index 61feb27..f6c119e 100644 +--- a/tests/shell/testcases/optimizations/dumps/merge_nat.nft ++++ b/tests/shell/testcases/optimizations/dumps/merge_nat.nft +@@ -19,14 +19,3 @@ table ip test4 { + tcp dport 85 redirect + } + } +-table inet nat { +- chain prerouting { +- oif "lo" accept +- dnat ip to iifname . ip daddr . tcp dport map { "enp2s0" . 72.2.3.70 . 80 : 10.1.1.52 . 80, "enp2s0" . 72.2.3.66 . 53122 : 10.1.1.10 . 22, "enp2s0" . 72.2.3.66 . 443 : 10.1.1.52 . 443 } +- } +- +- chain postrouting { +- oif "lo" accept +- snat ip to ip daddr map { 72.2.3.66 : 10.2.2.2, 72.2.3.67 : 10.2.3.3 } +- } +-} +diff --git a/tests/shell/testcases/optimizations/dumps/merge_nat_inet.nft b/tests/shell/testcases/optimizations/dumps/merge_nat_inet.nft +new file mode 100644 +index 0000000..a1a1135 +--- /dev/null ++++ b/tests/shell/testcases/optimizations/dumps/merge_nat_inet.nft +@@ -0,0 +1,11 @@ ++table inet nat { ++ chain prerouting { ++ oif "lo" accept ++ dnat ip to iifname . ip daddr . tcp dport map { "enp2s0" . 72.2.3.70 . 80 : 10.1.1.52 . 80, "enp2s0" . 72.2.3.66 . 53122 : 10.1.1.10 . 22, "enp2s0" . 72.2.3.66 . 443 : 10.1.1.52 . 443 } ++ } ++ ++ chain postrouting { ++ oif "lo" accept ++ snat ip to ip daddr map { 72.2.3.66 : 10.2.2.2, 72.2.3.67 : 10.2.3.3 } ++ } ++} +diff --git a/tests/shell/testcases/optimizations/merge_nat b/tests/shell/testcases/optimizations/merge_nat +index bfe9787..3ffcbd5 100755 +--- a/tests/shell/testcases/optimizations/merge_nat ++++ b/tests/shell/testcases/optimizations/merge_nat +@@ -36,19 +36,3 @@ RULESET="table ip test4 { + }" + + $NFT -o -f - <<< $RULESET +- +-RULESET="table inet nat { +- chain prerouting { +- oif lo accept +- iifname enp2s0 ip daddr 72.2.3.66 tcp dport 53122 dnat to 10.1.1.10:22 +- iifname enp2s0 ip daddr 72.2.3.66 tcp dport 443 dnat to 10.1.1.52:443 +- iifname enp2s0 ip daddr 72.2.3.70 tcp dport 80 dnat to 10.1.1.52:80 +- } +- chain postrouting { +- oif lo accept +- ip daddr 72.2.3.66 snat to 10.2.2.2 +- ip daddr 72.2.3.67 snat to 10.2.3.3 +- } +-}" +- +-$NFT -o -f - <<< $RULESET +diff --git a/tests/shell/testcases/optimizations/merge_nat_inet b/tests/shell/testcases/optimizations/merge_nat_inet +new file mode 100755 +index 0000000..ff1916d +--- /dev/null ++++ b/tests/shell/testcases/optimizations/merge_nat_inet +@@ -0,0 +1,21 @@ ++#!/bin/bash ++ ++# NFT_TEST_REQUIRES(NFT_TEST_HAVE_inet_nat) ++ ++set -e ++ ++RULESET="table inet nat { ++ chain prerouting { ++ oif lo accept ++ iifname enp2s0 ip daddr 72.2.3.66 tcp dport 53122 dnat to 10.1.1.10:22 ++ iifname enp2s0 ip daddr 72.2.3.66 tcp dport 443 dnat to 10.1.1.52:443 ++ iifname enp2s0 ip daddr 72.2.3.70 tcp dport 80 dnat to 10.1.1.52:80 ++ } ++ chain postrouting { ++ oif lo accept ++ ip daddr 72.2.3.66 snat to 10.2.2.2 ++ ip daddr 72.2.3.67 snat to 10.2.3.3 ++ } ++}" ++ ++$NFT -o -f - <<< $RULESET diff --git a/0039-evaluate-clone-unary-expression-datatype-to-deal-wit.patch b/0039-evaluate-clone-unary-expression-datatype-to-deal-wit.patch new file mode 100644 index 0000000..58e55df --- /dev/null +++ b/0039-evaluate-clone-unary-expression-datatype-to-deal-wit.patch @@ -0,0 +1,83 @@ +From bf752ee621e61026a40f050ac9d8a1eda351b81c Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:41 +0200 +Subject: [PATCH] evaluate: clone unary expression datatype to deal with + dynamic datatype + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit faa6908fad6053ae9549c45b88d0402cc69cf1ed + +commit faa6908fad6053ae9549c45b88d0402cc69cf1ed +Author: Pablo Neira Ayuso +Date: Wed Nov 22 20:35:07 2023 +0100 + + evaluate: clone unary expression datatype to deal with dynamic datatype + + When allocating a unary expression, clone the datatype to deal with + dynamic datatypes. + + Fixes: 6b01bb9ff798 ("datatype: concat expression only releases dynamically allocated datatype") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/evaluate.c | 2 +- + tests/shell/testcases/maps/dumps/vmap_unary.nft | 11 +++++++++++ + tests/shell/testcases/maps/vmap_unary | 17 +++++++++++++++++ + 3 files changed, 29 insertions(+), 1 deletion(-) + create mode 100644 tests/shell/testcases/maps/dumps/vmap_unary.nft + create mode 100755 tests/shell/testcases/maps/vmap_unary + +diff --git a/src/evaluate.c b/src/evaluate.c +index d204d3e..6f0fba6 100644 +--- a/src/evaluate.c ++++ b/src/evaluate.c +@@ -1245,7 +1245,7 @@ static int expr_evaluate_unary(struct eval_ctx *ctx, struct expr **expr) + BUG("invalid unary operation %u\n", unary->op); + } + +- unary->dtype = arg->dtype; ++ unary->dtype = datatype_clone(arg->dtype); + unary->byteorder = byteorder; + unary->len = arg->len; + return 0; +diff --git a/tests/shell/testcases/maps/dumps/vmap_unary.nft b/tests/shell/testcases/maps/dumps/vmap_unary.nft +new file mode 100644 +index 0000000..46c538b +--- /dev/null ++++ b/tests/shell/testcases/maps/dumps/vmap_unary.nft +@@ -0,0 +1,11 @@ ++table ip filter { ++ map ipsec_in { ++ typeof ipsec in reqid . iif : verdict ++ flags interval ++ } ++ ++ chain INPUT { ++ type filter hook input priority filter; policy drop; ++ ipsec in reqid . iif vmap @ipsec_in ++ } ++} +diff --git a/tests/shell/testcases/maps/vmap_unary b/tests/shell/testcases/maps/vmap_unary +new file mode 100755 +index 0000000..4038d1c +--- /dev/null ++++ b/tests/shell/testcases/maps/vmap_unary +@@ -0,0 +1,17 @@ ++#!/bin/bash ++ ++set -e ++ ++RULESET="table ip filter { ++ map ipsec_in { ++ typeof ipsec in reqid . iif : verdict ++ flags interval ++ } ++ ++ chain INPUT { ++ type filter hook input priority 0; policy drop ++ ipsec in reqid . iif vmap @ipsec_in ++ } ++}" ++ ++$NFT -f - <<< $RULESET diff --git a/0040-evaluate-reject-sets-with-no-key.patch b/0040-evaluate-reject-sets-with-no-key.patch new file mode 100644 index 0000000..9ffaee3 --- /dev/null +++ b/0040-evaluate-reject-sets-with-no-key.patch @@ -0,0 +1,65 @@ +From e0b42568ea971334ca2177f5275bfab4e8312e8c Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:41 +0200 +Subject: [PATCH] evaluate: reject sets with no key + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 09c573053ff03ad0a2eeb12c2957881648062f50 + +commit 09c573053ff03ad0a2eeb12c2957881648062f50 +Author: Florian Westphal +Date: Thu Nov 30 21:29:52 2023 +0100 + + evaluate: reject sets with no key + + nft --check -f tests/shell/testcases/bogons/nft-f/set_without_key + Segmentation fault (core dumped) + + Fixes: 56c90a2dd2eb ("evaluate: expand sets and maps before evaluation") + Signed-off-by: Florian Westphal + +Signed-off-by: Phil Sutter +--- + src/evaluate.c | 3 +++ + tests/shell/testcases/bogons/nft-f/map_without_key | 5 +++++ + tests/shell/testcases/bogons/nft-f/set_without_key | 5 +++++ + 3 files changed, 13 insertions(+) + create mode 100644 tests/shell/testcases/bogons/nft-f/map_without_key + create mode 100644 tests/shell/testcases/bogons/nft-f/set_without_key + +diff --git a/src/evaluate.c b/src/evaluate.c +index 6f0fba6..2955ac5 100644 +--- a/src/evaluate.c ++++ b/src/evaluate.c +@@ -4616,6 +4616,9 @@ static int elems_evaluate(struct eval_ctx *ctx, struct set *set) + { + ctx->set = set; + if (set->init != NULL) { ++ if (set->key == NULL) ++ return set_error(ctx, set, "set definition does not specify key"); ++ + __expr_set_context(&ctx->ectx, set->key->dtype, + set->key->byteorder, set->key->len, 0); + if (expr_evaluate(ctx, &set->init) < 0) +diff --git a/tests/shell/testcases/bogons/nft-f/map_without_key b/tests/shell/testcases/bogons/nft-f/map_without_key +new file mode 100644 +index 0000000..78f16b2 +--- /dev/null ++++ b/tests/shell/testcases/bogons/nft-f/map_without_key +@@ -0,0 +1,5 @@ ++table t { ++ map m { ++ elements = { 0x00000023 : 0x00001337 } ++ } ++} +diff --git a/tests/shell/testcases/bogons/nft-f/set_without_key b/tests/shell/testcases/bogons/nft-f/set_without_key +new file mode 100644 +index 0000000..f194afb +--- /dev/null ++++ b/tests/shell/testcases/bogons/nft-f/set_without_key +@@ -0,0 +1,5 @@ ++table ip t { ++ set s { ++ elements = { 0x00000023-0x00000142, 0x00001337 } ++ } ++} diff --git a/0041-evaluate-prevent-assert-when-evaluating-very-large-s.patch b/0041-evaluate-prevent-assert-when-evaluating-very-large-s.patch new file mode 100644 index 0000000..4a51b01 --- /dev/null +++ b/0041-evaluate-prevent-assert-when-evaluating-very-large-s.patch @@ -0,0 +1,59 @@ +From 8523567b0fc4d2c0f978ee43234d984d5e75795c Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:41 +0200 +Subject: [PATCH] evaluate: prevent assert when evaluating very large shift + values + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 26723202e600604ab7cf48915507cfcb7a313620 + +commit 26723202e600604ab7cf48915507cfcb7a313620 +Author: Florian Westphal +Date: Fri Dec 1 14:16:14 2023 +0100 + + evaluate: prevent assert when evaluating very large shift values + + Error out instead of 'nft: gmputil.c:67: mpz_get_uint32: Assertion `cnt <= 1' failed.'. + + Fixes: edecd58755a8 ("evaluate: support shifts larger than the width of the left operand") + Signed-off-by: Florian Westphal + +Signed-off-by: Phil Sutter +--- + src/evaluate.c | 9 +++++++-- + tests/shell/testcases/bogons/nft-f/huge_shift_assert | 5 +++++ + 2 files changed, 12 insertions(+), 2 deletions(-) + create mode 100644 tests/shell/testcases/bogons/nft-f/huge_shift_assert + +diff --git a/src/evaluate.c b/src/evaluate.c +index 2955ac5..1d13db1 100644 +--- a/src/evaluate.c ++++ b/src/evaluate.c +@@ -1312,9 +1312,14 @@ static int constant_binop_simplify(struct eval_ctx *ctx, struct expr **expr) + static int expr_evaluate_shift(struct eval_ctx *ctx, struct expr **expr) + { + struct expr *op = *expr, *left = op->left, *right = op->right; +- unsigned int shift = mpz_get_uint32(right->value); +- unsigned int max_shift_len; ++ unsigned int shift, max_shift_len; + ++ /* mpz_get_uint32 has assert() for huge values */ ++ if (mpz_cmp_ui(right->value, UINT_MAX) > 0) ++ return expr_binary_error(ctx->msgs, right, left, ++ "shifts exceeding %u bits are not supported", UINT_MAX); ++ ++ shift = mpz_get_uint32(right->value); + if (ctx->stmt_len > left->len) + max_shift_len = ctx->stmt_len; + else +diff --git a/tests/shell/testcases/bogons/nft-f/huge_shift_assert b/tests/shell/testcases/bogons/nft-f/huge_shift_assert +new file mode 100644 +index 0000000..7599f85 +--- /dev/null ++++ b/tests/shell/testcases/bogons/nft-f/huge_shift_assert +@@ -0,0 +1,5 @@ ++table ip t { ++ chain c { ++ counter name meta mark >> 88888888888888888888 ++ } ++} diff --git a/0042-evaluate-disable-meta-set-with-ranges.patch b/0042-evaluate-disable-meta-set-with-ranges.patch new file mode 100644 index 0000000..f28efe8 --- /dev/null +++ b/0042-evaluate-disable-meta-set-with-ranges.patch @@ -0,0 +1,70 @@ +From 04adeaaab4d640f4bb50e783db835909df9959d4 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:41 +0200 +Subject: [PATCH] evaluate: disable meta set with ranges + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit d99b44adc5cfc455fdafd9b4bdabd413edf9a38a + +commit d99b44adc5cfc455fdafd9b4bdabd413edf9a38a +Author: Florian Westphal +Date: Mon Dec 4 19:04:58 2023 +0100 + + evaluate: disable meta set with ranges + + ... this will cause an assertion in netlink linearization, catch this + at eval stage instead. + + before: + BUG: unknown expression type range + nft: netlink_linearize.c:908: netlink_gen_expr: Assertion `0' failed. + + after: + /unknown_expr_type_range_assert:3:31-40: Error: Meta expression cannot be a range + meta mark set 0x001-3434 + ^^^^^^^^^^ + + Signed-off-by: Florian Westphal + +Signed-off-by: Phil Sutter +--- + src/evaluate.c | 13 +++++++++++++ + .../bogons/nft-f/unknown_expr_type_range_assert | 5 +++++ + 2 files changed, 18 insertions(+) + create mode 100644 tests/shell/testcases/bogons/nft-f/unknown_expr_type_range_assert + +diff --git a/src/evaluate.c b/src/evaluate.c +index 1d13db1..9de71d7 100644 +--- a/src/evaluate.c ++++ b/src/evaluate.c +@@ -3148,6 +3148,19 @@ static int stmt_evaluate_meta(struct eval_ctx *ctx, struct stmt *stmt) + &stmt->meta.expr); + ctx->stmt_len = 0; + ++ if (ret < 0) ++ return ret; ++ ++ switch (stmt->meta.expr->etype) { ++ case EXPR_RANGE: ++ ret = expr_error(ctx->msgs, stmt->meta.expr, ++ "Meta expression cannot be a range"); ++ break; ++ default: ++ break; ++ ++ } ++ + return ret; + } + +diff --git a/tests/shell/testcases/bogons/nft-f/unknown_expr_type_range_assert b/tests/shell/testcases/bogons/nft-f/unknown_expr_type_range_assert +new file mode 100644 +index 0000000..234dd62 +--- /dev/null ++++ b/tests/shell/testcases/bogons/nft-f/unknown_expr_type_range_assert +@@ -0,0 +1,5 @@ ++table ip x { ++ chain k { ++ meta mark set 0x001-3434 ++ } ++} diff --git a/0043-monitor-add-support-for-concatenated-set-ranges.patch b/0043-monitor-add-support-for-concatenated-set-ranges.patch new file mode 100644 index 0000000..7bcb944 --- /dev/null +++ b/0043-monitor-add-support-for-concatenated-set-ranges.patch @@ -0,0 +1,78 @@ +From 4559a6cc20f887f7763153790f01449da18bf1cd Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:41 +0200 +Subject: [PATCH] monitor: add support for concatenated set ranges + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 0d9392eef5f2c79ac7c19f59754a0aee574b5617 + +commit 0d9392eef5f2c79ac7c19f59754a0aee574b5617 +Author: Pablo Neira Ayuso +Date: Tue Dec 5 17:20:05 2023 +0100 + + monitor: add support for concatenated set ranges + + monitor is missing concatenated set ranges support. + + Fixes: 8ac2f3b2fca3 ("src: Add support for concatenated set ranges") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/monitor.c | 11 +++++++++-- + tests/monitor/testcases/set-concat-interval.t | 12 ++++++++++++ + 2 files changed, 21 insertions(+), 2 deletions(-) + create mode 100644 tests/monitor/testcases/set-concat-interval.t + +diff --git a/src/monitor.c b/src/monitor.c +index 82762a0..2fc16d6 100644 +--- a/src/monitor.c ++++ b/src/monitor.c +@@ -390,13 +390,19 @@ static bool netlink_event_range_cache(struct set *cached_set, + + /* don't cache half-open range elements */ + elem = list_entry(dummyset->init->expressions.prev, struct expr, list); +- if (!set_elem_is_open_interval(elem)) { ++ if (!set_elem_is_open_interval(elem) && ++ dummyset->desc.field_count <= 1) { + cached_set->rg_cache = expr_clone(elem); + return true; + } + + out_decompose: +- interval_map_decompose(dummyset->init); ++ if (dummyset->flags & NFT_SET_INTERVAL && ++ dummyset->desc.field_count > 1) ++ concat_range_aggregate(dummyset->init); ++ else ++ interval_map_decompose(dummyset->init); ++ + return false; + } + +@@ -437,6 +443,7 @@ static int netlink_events_setelem_cb(const struct nlmsghdr *nlh, int type, + dummyset->data = expr_clone(set->data); + dummyset->flags = set->flags; + dummyset->init = set_expr_alloc(monh->loc, set); ++ dummyset->desc.field_count = set->desc.field_count; + + nlsei = nftnl_set_elems_iter_create(nls); + if (nlsei == NULL) +diff --git a/tests/monitor/testcases/set-concat-interval.t b/tests/monitor/testcases/set-concat-interval.t +new file mode 100644 +index 0000000..763dc31 +--- /dev/null ++++ b/tests/monitor/testcases/set-concat-interval.t +@@ -0,0 +1,12 @@ ++# setup first ++I add table ip t ++I add chain ip t c ++O - ++J {"add": {"table": {"family": "ip", "name": "t", "handle": 0}}} ++J {"add": {"chain": {"family": "ip", "table": "t", "name": "c", "handle": 0}}} ++ ++# add set with elements, monitor output expectedly differs ++I add map ip t s { typeof udp length . @ih,32,32 : verdict; flags interval; elements = { 20-80 . 0x14 : accept, 1-10 . 0xa : drop }; } ++O add map ip t s { typeof udp length . @ih,32,32 : verdict; flags interval; } ++O add element ip t s { 20-80 . 0x14 : accept } ++O add element ip t s { 1-10 . 0xa : drop } diff --git a/0044-evaluate-reject-set-definition-with-no-key.patch b/0044-evaluate-reject-set-definition-with-no-key.patch new file mode 100644 index 0000000..e242a2f --- /dev/null +++ b/0044-evaluate-reject-set-definition-with-no-key.patch @@ -0,0 +1,55 @@ +From 1ccb4065209aef5be9e922c8702317636ca67745 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:41 +0200 +Subject: [PATCH] evaluate: reject set definition with no key + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 1949a63215b423b914d3a7a9de7511cb48af3c09 + +commit 1949a63215b423b914d3a7a9de7511cb48af3c09 +Author: Pablo Neira Ayuso +Date: Wed Dec 6 13:40:22 2023 +0100 + + evaluate: reject set definition with no key + + tests/shell/testcases/bogons/nft-f/set_definition_with_no_key_assert + BUG: unhandled key type 2 + nft: src/intervals.c:59: setelem_expr_to_range: Assertion `0' failed. + + This patch adds a new unit tests/shell courtesy of Florian Westphal. + + Fixes: 3975430b12d9 ("src: expand table command before evaluation") + Reported-by: Florian Westphal + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/evaluate.c | 8 ++++++-- + 1 file changed, 6 insertions(+), 2 deletions(-) + +diff --git a/src/evaluate.c b/src/evaluate.c +index 9de71d7..c2a7a41 100644 +--- a/src/evaluate.c ++++ b/src/evaluate.c +@@ -4664,6 +4664,12 @@ static int set_evaluate(struct eval_ctx *ctx, struct set *set) + struct stmt *stmt; + const char *type; + ++ type = set_is_map(set->flags) ? "map" : "set"; ++ ++ if (set->key == NULL) ++ return set_error(ctx, set, "%s definition does not specify key", ++ type); ++ + if (!set_is_anonymous(set->flags)) { + table = table_cache_find(&ctx->nft->cache.table_cache, + set->handle.table.name, +@@ -4687,8 +4693,6 @@ static int set_evaluate(struct eval_ctx *ctx, struct set *set) + if (!(set->flags & NFT_SET_INTERVAL) && set->automerge) + return set_error(ctx, set, "auto-merge only works with interval sets"); + +- type = set_is_map(set->flags) ? "map" : "set"; +- + if (set->key == NULL) + return set_error(ctx, set, "%s definition does not specify key", + type); diff --git a/0045-parser-tcpopt-fix-tcp-option-parsing-with-NUM-length.patch b/0045-parser-tcpopt-fix-tcp-option-parsing-with-NUM-length.patch new file mode 100644 index 0000000..58896c2 --- /dev/null +++ b/0045-parser-tcpopt-fix-tcp-option-parsing-with-NUM-length.patch @@ -0,0 +1,210 @@ +From 039d41bcd5dc8728690f5e9a6cf7c1b68bd05905 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:41 +0200 +Subject: [PATCH] parser: tcpopt: fix tcp option parsing with NUM + length + field +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 59a33d08ab3a75b2ae370b6816942793f49fa8db + +commit 59a33d08ab3a75b2ae370b6816942793f49fa8db +Author: Florian Westphal +Date: Tue Dec 5 12:56:08 2023 +0100 + + parser: tcpopt: fix tcp option parsing with NUM + length field + + tcp option 254 length ge 4 + + ... will segfault. + The crash bug is that tcpopt_expr_alloc() can return NULL if we cannot + find a suitable template for the requested kind + field combination, + so add the needed error handling in the bison parser. + + However, we can handle this. NOP and EOL have templates, all other + options (known or unknown) must also have a length field. + + So also add a fallback template to handle both kind and length, even + if only a numeric option is given that nft doesn't recognize. + + Don't bother with output, above will be printed via raw syntax, i.e. + tcp option @254,8,8 >= 4. + + Fixes: 24d8da308342 ("tcpopt: allow to check for presence of any tcp option") + Reported-by: Maciej Å»enczykowski + Signed-off-by: Florian Westphal + +Signed-off-by: Phil Sutter +--- + src/parser_bison.y | 4 ++ + src/tcpopt.c | 44 +++++++++++---- + .../packetpath/dumps/tcp_options.nodump | 0 + tests/shell/testcases/packetpath/tcp_options | 55 +++++++++++++++++++ + 4 files changed, 93 insertions(+), 10 deletions(-) + create mode 100644 tests/shell/testcases/packetpath/dumps/tcp_options.nodump + create mode 100755 tests/shell/testcases/packetpath/tcp_options + +diff --git a/src/parser_bison.y b/src/parser_bison.y +index 751b3e1..9386a9c 100644 +--- a/src/parser_bison.y ++++ b/src/parser_bison.y +@@ -5837,6 +5837,10 @@ tcp_hdr_expr : TCP tcp_hdr_field + | TCP OPTION tcp_hdr_option_kind_and_field + { + $$ = tcpopt_expr_alloc(&@$, $3.kind, $3.field); ++ if ($$ == NULL) { ++ erec_queue(error(&@1, "Could not find a tcp option template"), state->msgs); ++ YYERROR; ++ } + } + | TCP OPTION AT close_scope_at tcp_hdr_option_type COMMA NUM COMMA NUM + { +diff --git a/src/tcpopt.c b/src/tcpopt.c +index 3fcb273..8111a50 100644 +--- a/src/tcpopt.c ++++ b/src/tcpopt.c +@@ -118,6 +118,13 @@ static const struct exthdr_desc tcpopt_mptcp = { + [TCPOPT_MPTCP_SUBTYPE] = PHT("subtype", 16, 4), + }, + }; ++ ++static const struct exthdr_desc tcpopt_fallback = { ++ .templates = { ++ [TCPOPT_COMMON_KIND] = PHT("kind", 0, 8), ++ [TCPOPT_COMMON_LENGTH] = PHT("length", 8, 8), ++ }, ++}; + #undef PHT + + const struct exthdr_desc *tcpopt_protocols[] = { +@@ -133,6 +140,17 @@ const struct exthdr_desc *tcpopt_protocols[] = { + [TCPOPT_KIND_FASTOPEN] = &tcpopt_fastopen, + }; + ++static void tcpopt_assign_tmpl(struct expr *expr, ++ const struct proto_hdr_template *tmpl, ++ const struct exthdr_desc *desc) ++{ ++ expr->exthdr.op = NFT_EXTHDR_OP_TCPOPT; ++ ++ expr->exthdr.desc = desc; ++ expr->exthdr.tmpl = tmpl; ++ expr->exthdr.offset = tmpl->offset; ++} ++ + /** + * tcpopt_expr_alloc - allocate tcp option extension expression + * +@@ -182,18 +200,26 @@ struct expr *tcpopt_expr_alloc(const struct location *loc, + desc = tcpopt_protocols[kind]; + + if (!desc) { +- if (field != TCPOPT_COMMON_KIND || kind > 255) ++ if (kind > 255) + return NULL; + ++ desc = &tcpopt_fallback; ++ ++ switch (field) { ++ case TCPOPT_COMMON_KIND: ++ case TCPOPT_COMMON_LENGTH: ++ tmpl = &desc->templates[field]; ++ break; ++ default: ++ tmpl = &tcpopt_unknown_template; ++ break; ++ } ++ + expr = expr_alloc(loc, EXPR_EXTHDR, &integer_type, + BYTEORDER_BIG_ENDIAN, 8); + +- desc = tcpopt_protocols[TCPOPT_NOP]; +- tmpl = &desc->templates[field]; +- expr->exthdr.desc = desc; +- expr->exthdr.tmpl = tmpl; +- expr->exthdr.op = NFT_EXTHDR_OP_TCPOPT; + expr->exthdr.raw_type = kind; ++ tcpopt_assign_tmpl(expr, tmpl, desc); + return expr; + } + +@@ -203,11 +229,9 @@ struct expr *tcpopt_expr_alloc(const struct location *loc, + + expr = expr_alloc(loc, EXPR_EXTHDR, tmpl->dtype, + BYTEORDER_BIG_ENDIAN, tmpl->len); +- expr->exthdr.desc = desc; +- expr->exthdr.tmpl = tmpl; +- expr->exthdr.op = NFT_EXTHDR_OP_TCPOPT; ++ + expr->exthdr.raw_type = desc->type; +- expr->exthdr.offset = tmpl->offset; ++ tcpopt_assign_tmpl(expr, tmpl, desc); + + return expr; + } +diff --git a/tests/shell/testcases/packetpath/dumps/tcp_options.nodump b/tests/shell/testcases/packetpath/dumps/tcp_options.nodump +new file mode 100644 +index 0000000..e69de29 +diff --git a/tests/shell/testcases/packetpath/tcp_options b/tests/shell/testcases/packetpath/tcp_options +new file mode 100755 +index 0000000..1c9ee53 +--- /dev/null ++++ b/tests/shell/testcases/packetpath/tcp_options +@@ -0,0 +1,55 @@ ++#!/bin/bash ++ ++have_socat="no" ++socat -h > /dev/null && have_socat="yes" ++ ++ip link set lo up ++ ++$NFT -f /dev/stdin < connect fails with eperm) ++socat -u STDIN TCP:127.0.0.1:22345,connect-timeout=1 < /dev/null > /dev/null ++ ++# can't validate via dump file, syn rexmit can cause counters to be > 1 in rare cases. ++ ++$NFT list counter inet t nomatchc ++ ++# nomatchc must be 0. ++$NFT list counter inet t nomatchc | grep -q "packets 0" || exit 1 ++ ++# these counters must not be 0. ++for nz in sackpermc maxsegc nopc; do ++ $NFT list counter inet t $nz ++ $NFT list counter inet t $nz | grep -q "packets 0" && exit 1 ++done ++ ++exit 0 diff --git a/0046-evaluate-reset-statement-length-context-before-evalu.patch b/0046-evaluate-reset-statement-length-context-before-evalu.patch new file mode 100644 index 0000000..754d87c --- /dev/null +++ b/0046-evaluate-reset-statement-length-context-before-evalu.patch @@ -0,0 +1,344 @@ +From dca8ddacdacbad26f92b7cd665dd150a341ced26 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:41 +0200 +Subject: [PATCH] evaluate: reset statement length context before evaluating + statement + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 8d3de823b622136e1d05a6fed11ff2dc0e804f8a + +commit 8d3de823b622136e1d05a6fed11ff2dc0e804f8a +Author: Pablo Neira Ayuso +Date: Wed Dec 6 18:48:29 2023 +0100 + + evaluate: reset statement length context before evaluating statement + + This patch consolidates ctx->stmt_len reset in stmt_evaluate() to avoid + this problem. Note that stmt_evaluate_meta() and stmt_evaluate_ct() + already reset it after the statement evaluation. + + Moreover, statement dependency can be generated while evaluating a meta + and ct statement. Payload statement dependency already manually stashes + this before calling stmt_evaluate(). Add a new stmt_dependency_evaluate() + function to stash statement length context when evaluating a new statement + dependency and use it for all of the existing statement dependencies. + + Florian also says: + + 'meta mark set vlan id map { 1 : 0x00000001, 4095 : 0x00004095 }' will + crash. Reason is that the l2 dependency generated here is errounously + expanded to a 32bit-one, so the evaluation path won't recognize this + as a L2 dependency. Therefore, pctx->stacked_ll_count is 0 and + __expr_evaluate_payload() crashes with a null deref when + dereferencing pctx->stacked_ll[0]. + + nft-test.py gains a fugly hack to tolerate '!map typeof vlan id : meta mark'. + For more generic support we should find something more acceptable, e.g. + + !map typeof( everything here is a key or data ) timeout ... + + tests/py update and assert(pctx->stacked_ll_count) by Florian Westphal. + + Fixes: edecd58755a8 ("evaluate: support shifts larger than the width of the left operand") + Signed-off-by: Pablo Neira Ayuso + Signed-off-by: Florian Westphal + +Signed-off-by: Phil Sutter +--- + include/statement.h | 1 + + src/evaluate.c | 26 ++++++++++++++++++-------- + src/payload.c | 29 +++++++---------------------- + tests/py/any/meta.t | 4 ++++ + tests/py/any/meta.t.payload | 25 +++++++++++++++++++++++++ + tests/py/any/meta.t.payload.bridge | 20 ++++++++++++++++++++ + tests/py/nft-test.py | 17 +++++++++++++---- + 7 files changed, 88 insertions(+), 34 deletions(-) + create mode 100644 tests/py/any/meta.t.payload.bridge + +diff --git a/include/statement.h b/include/statement.h +index 720a6ac..662f99d 100644 +--- a/include/statement.h ++++ b/include/statement.h +@@ -416,6 +416,7 @@ struct stmt { + extern struct stmt *stmt_alloc(const struct location *loc, + const struct stmt_ops *ops); + int stmt_evaluate(struct eval_ctx *ctx, struct stmt *stmt); ++int stmt_dependency_evaluate(struct eval_ctx *ctx, struct stmt *stmt); + extern void stmt_free(struct stmt *stmt); + extern void stmt_list_free(struct list_head *list); + extern void stmt_print(const struct stmt *stmt, struct output_ctx *octx); +diff --git a/src/evaluate.c b/src/evaluate.c +index c2a7a41..cfdc6c0 100644 +--- a/src/evaluate.c ++++ b/src/evaluate.c +@@ -454,6 +454,18 @@ static int expr_evaluate_primary(struct eval_ctx *ctx, struct expr **expr) + return 0; + } + ++int stmt_dependency_evaluate(struct eval_ctx *ctx, struct stmt *stmt) ++{ ++ uint32_t stmt_len = ctx->stmt_len; ++ ++ if (stmt_evaluate(ctx, stmt) < 0) ++ return stmt_error(ctx, stmt, "dependency statement is invalid"); ++ ++ ctx->stmt_len = stmt_len; ++ ++ return 0; ++} ++ + static int + conflict_resolution_gen_dependency(struct eval_ctx *ctx, int protocol, + const struct expr *expr, +@@ -479,7 +491,7 @@ conflict_resolution_gen_dependency(struct eval_ctx *ctx, int protocol, + + dep = relational_expr_alloc(&expr->location, OP_EQ, left, right); + stmt = expr_stmt_alloc(&dep->location, dep); +- if (stmt_evaluate(ctx, stmt) < 0) ++ if (stmt_dependency_evaluate(ctx, stmt) < 0) + return expr_error(ctx->msgs, expr, + "dependency statement is invalid"); + +@@ -696,9 +708,8 @@ static int meta_iiftype_gen_dependency(struct eval_ctx *ctx, + "for this family"); + + nstmt = meta_stmt_meta_iiftype(&payload->location, type); +- if (stmt_evaluate(ctx, nstmt) < 0) +- return expr_error(ctx->msgs, payload, +- "dependency statement is invalid"); ++ if (stmt_dependency_evaluate(ctx, nstmt) < 0) ++ return -1; + + if (ctx->inner_desc) + nstmt->expr->left->meta.inner_desc = ctx->inner_desc; +@@ -809,6 +820,7 @@ static int __expr_evaluate_payload(struct eval_ctx *ctx, struct expr *expr) + desc->name, + payload->payload.desc->name); + ++ assert(pctx->stacked_ll_count); + payload->payload.offset += pctx->stacked_ll[0]->length; + rule_stmt_insert_at(ctx->rule, nstmt, ctx->stmt); + return 1; +@@ -3146,8 +3158,6 @@ static int stmt_evaluate_meta(struct eval_ctx *ctx, struct stmt *stmt) + stmt->meta.tmpl->len, + stmt->meta.tmpl->byteorder, + &stmt->meta.expr); +- ctx->stmt_len = 0; +- + if (ret < 0) + return ret; + +@@ -3175,8 +3185,6 @@ static int stmt_evaluate_ct(struct eval_ctx *ctx, struct stmt *stmt) + stmt->ct.tmpl->len, + stmt->ct.tmpl->byteorder, + &stmt->ct.expr); +- ctx->stmt_len = 0; +- + if (ret < 0) + return -1; + +@@ -4468,6 +4476,8 @@ int stmt_evaluate(struct eval_ctx *ctx, struct stmt *stmt) + erec_destroy(erec); + } + ++ ctx->stmt_len = 0; ++ + switch (stmt->ops->type) { + case STMT_CONNLIMIT: + case STMT_COUNTER: +diff --git a/src/payload.c b/src/payload.c +index 140ca50..5de3d32 100644 +--- a/src/payload.c ++++ b/src/payload.c +@@ -407,7 +407,6 @@ static int payload_add_dependency(struct eval_ctx *ctx, + const struct proto_hdr_template *tmpl; + struct expr *dep, *left, *right; + struct proto_ctx *pctx; +- unsigned int stmt_len; + struct stmt *stmt; + int protocol; + +@@ -429,15 +428,9 @@ static int payload_add_dependency(struct eval_ctx *ctx, + + dep = relational_expr_alloc(&expr->location, OP_EQ, left, right); + +- stmt_len = ctx->stmt_len; +- ctx->stmt_len = 0; +- + stmt = expr_stmt_alloc(&dep->location, dep); +- if (stmt_evaluate(ctx, stmt) < 0) { +- return expr_error(ctx->msgs, expr, +- "dependency statement is invalid"); +- } +- ctx->stmt_len = stmt_len; ++ if (stmt_dependency_evaluate(ctx, stmt) < 0) ++ return -1; + + if (ctx->inner_desc) { + if (tmpl->meta_key) +@@ -547,7 +540,6 @@ int payload_gen_dependency(struct eval_ctx *ctx, const struct expr *expr, + const struct hook_proto_desc *h; + const struct proto_desc *desc; + struct proto_ctx *pctx; +- unsigned int stmt_len; + struct stmt *stmt; + uint16_t type; + +@@ -564,17 +556,11 @@ int payload_gen_dependency(struct eval_ctx *ctx, const struct expr *expr, + "protocol specification is invalid " + "for this family"); + +- stmt_len = ctx->stmt_len; +- ctx->stmt_len = 0; +- + stmt = meta_stmt_meta_iiftype(&expr->location, type); +- if (stmt_evaluate(ctx, stmt) < 0) { +- return expr_error(ctx->msgs, expr, +- "dependency statement is invalid"); +- } +- *res = stmt; ++ if (stmt_dependency_evaluate(ctx, stmt) < 0) ++ return -1; + +- ctx->stmt_len = stmt_len; ++ *res = stmt; + + return 0; + } +@@ -1442,9 +1428,8 @@ int payload_gen_icmp_dependency(struct eval_ctx *ctx, const struct expr *expr, + + pctx->th_dep.icmp.type = type; + +- if (stmt_evaluate(ctx, stmt) < 0) +- return expr_error(ctx->msgs, expr, +- "icmp dependency statement is invalid"); ++ if (stmt_dependency_evaluate(ctx, stmt) < 0) ++ return -1; + done: + *res = stmt; + return 0; +diff --git a/tests/py/any/meta.t b/tests/py/any/meta.t +index 12fabb7..718c7ad 100644 +--- a/tests/py/any/meta.t ++++ b/tests/py/any/meta.t +@@ -224,3 +224,7 @@ time > "2022-07-01 11:00:00" accept;ok;meta time > "2022-07-01 11:00:00" accept + meta time "meh";fail + meta hour "24:00" drop;fail + meta day 7 drop;fail ++ ++meta mark set vlan id map { 1 : 0x00000001, 4095 : 0x00004095 };ok ++!map1 typeof vlan id : meta mark;ok ++meta mark set vlan id map @map1;ok +diff --git a/tests/py/any/meta.t.payload b/tests/py/any/meta.t.payload +index 16dc121..49dd729 100644 +--- a/tests/py/any/meta.t.payload ++++ b/tests/py/any/meta.t.payload +@@ -1072,3 +1072,28 @@ ip test-ip4 input + [ byteorder reg 1 = hton(reg 1, 8, 8) ] + [ cmp gt reg 1 0xf3a8fd16 0x00a07719 ] + [ immediate reg 0 accept ] ++ ++# meta mark set vlan id map { 1 : 0x00000001, 4095 : 0x00004095 } ++__map%d test-ip4 b size 2 ++__map%d test-ip4 0 ++ element 00000100 : 00000001 0 [end] element 0000ff0f : 00004095 0 [end] ++ip test-ip4 input ++ [ meta load iiftype => reg 1 ] ++ [ cmp eq reg 1 0x00000001 ] ++ [ payload load 2b @ link header + 12 => reg 1 ] ++ [ cmp eq reg 1 0x00000081 ] ++ [ payload load 2b @ link header + 14 => reg 1 ] ++ [ bitwise reg 1 = ( reg 1 & 0x0000ff0f ) ^ 0x00000000 ] ++ [ lookup reg 1 set __map%d dreg 1 ] ++ [ meta set mark with reg 1 ] ++ ++# meta mark set vlan id map @map1 ++ip test-ip4 input ++ [ meta load iiftype => reg 1 ] ++ [ cmp eq reg 1 0x00000001 ] ++ [ payload load 2b @ link header + 12 => reg 1 ] ++ [ cmp eq reg 1 0x00000081 ] ++ [ payload load 2b @ link header + 14 => reg 1 ] ++ [ bitwise reg 1 = ( reg 1 & 0x0000ff0f ) ^ 0x00000000 ] ++ [ lookup reg 1 set map1 dreg 1 ] ++ [ meta set mark with reg 1 ] +diff --git a/tests/py/any/meta.t.payload.bridge b/tests/py/any/meta.t.payload.bridge +new file mode 100644 +index 0000000..5997ccc +--- /dev/null ++++ b/tests/py/any/meta.t.payload.bridge +@@ -0,0 +1,20 @@ ++# meta mark set vlan id map { 1 : 0x00000001, 4095 : 0x00004095 } ++__map%d test-bridge b size 2 ++__map%d test-bridge 0 ++ element 00000100 : 00000001 0 [end] element 0000ff0f : 00004095 0 [end] ++bridge test-bridge input ++ [ payload load 2b @ link header + 12 => reg 1 ] ++ [ cmp eq reg 1 0x00000081 ] ++ [ payload load 2b @ link header + 14 => reg 1 ] ++ [ bitwise reg 1 = ( reg 1 & 0x0000ff0f ) ^ 0x00000000 ] ++ [ lookup reg 1 set __map%d dreg 1 ] ++ [ meta set mark with reg 1 ] ++ ++# meta mark set vlan id map @map1 ++bridge test-bridge input ++ [ payload load 2b @ link header + 12 => reg 1 ] ++ [ cmp eq reg 1 0x00000081 ] ++ [ payload load 2b @ link header + 14 => reg 1 ] ++ [ bitwise reg 1 = ( reg 1 & 0x0000ff0f ) ^ 0x00000000 ] ++ [ lookup reg 1 set map1 dreg 1 ] ++ [ meta set mark with reg 1 ] +diff --git a/tests/py/nft-test.py b/tests/py/nft-test.py +index 9a25503..a7d27c2 100755 +--- a/tests/py/nft-test.py ++++ b/tests/py/nft-test.py +@@ -368,9 +368,9 @@ def set_add(s, test_result, filename, lineno): + flags = "flags %s; " % flags + + if s.data == "": +- cmd = "add set %s %s { type %s;%s %s}" % (table, s.name, s.type, s.timeout, flags) ++ cmd = "add set %s %s { %s;%s %s}" % (table, s.name, s.type, s.timeout, flags) + else: +- cmd = "add map %s %s { type %s : %s;%s %s}" % (table, s.name, s.type, s.data, s.timeout, flags) ++ cmd = "add map %s %s { %s : %s;%s %s}" % (table, s.name, s.type, s.data, s.timeout, flags) + + ret = execute_cmd(cmd, filename, lineno) + +@@ -410,7 +410,7 @@ def map_add(s, test_result, filename, lineno): + if flags != "": + flags = "flags %s; " % flags + +- cmd = "add map %s %s { type %s : %s;%s %s}" % (table, s.name, s.type, s.data, s.timeout, flags) ++ cmd = "add map %s %s { %s : %s;%s %s}" % (table, s.name, s.type, s.data, s.timeout, flags) + + ret = execute_cmd(cmd, filename, lineno) + +@@ -1144,11 +1144,16 @@ def set_process(set_line, filename, lineno): + + tokens = set_line[0].split(" ") + set_name = tokens[0] +- set_type = tokens[2] ++ parse_typeof = tokens[1] == "typeof" ++ set_type = tokens[1] + " " + tokens[2] + set_data = "" + set_flags = "" + + i = 3 ++ if parse_typeof and tokens[i] == "id": ++ set_type += " " + tokens[i] ++ i += 1; ++ + while len(tokens) > i and tokens[i] == ".": + set_type += " . " + tokens[i+1] + i += 2 +@@ -1157,6 +1162,10 @@ def set_process(set_line, filename, lineno): + set_data = tokens[i+1] + i += 2 + ++ if parse_typeof and tokens[i] == "mark": ++ set_data += " " + tokens[i] ++ i += 1; ++ + if len(tokens) == i+2 and tokens[i] == "timeout": + timeout = "timeout " + tokens[i+1] + ";" + i += 2 diff --git a/0047-tests-py-missing-json-output-in-meta.t-with-vlan-map.patch b/0047-tests-py-missing-json-output-in-meta.t-with-vlan-map.patch new file mode 100644 index 0000000..3e34a7b --- /dev/null +++ b/0047-tests-py-missing-json-output-in-meta.t-with-vlan-map.patch @@ -0,0 +1,95 @@ +From 6b25abca848fe00a46536c61bd6770d219f965c8 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:41 +0200 +Subject: [PATCH] tests: py: missing json output in meta.t with vlan mapping + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 5fec559727ffd2c6c8958748beab782096385758 + +commit 5fec559727ffd2c6c8958748beab782096385758 +Author: Pablo Neira Ayuso +Date: Mon Dec 11 12:54:35 2023 +0100 + + tests: py: missing json output in meta.t with vlan mapping + + Fix this warning due to missing coverage: + + tests/py/any/meta.t.json.got: WARNING: line 2: Wrote JSON equivalent for rule meta mark set vlan id map { 1 : 0x00000001, 4095 : 0x00004095 } + ERROR: did not find JSON equivalent for rule 'meta mark set vlan id map @map1 + + Fixes: 8d3de823b622 ("evaluate: reset statement length context before evaluating statement") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + tests/py/any/meta.t.json | 60 ++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 60 insertions(+) + +diff --git a/tests/py/any/meta.t.json b/tests/py/any/meta.t.json +index 4734bbf..d50272d 100644 +--- a/tests/py/any/meta.t.json ++++ b/tests/py/any/meta.t.json +@@ -2758,3 +2758,63 @@ + "accept": null + } + ] ++ ++# meta mark set vlan id map { 1 : 0x00000001, 4095 : 0x00004095 } ++[ ++ { ++ "mangle": { ++ "key": { ++ "meta": { ++ "key": "mark" ++ } ++ }, ++ "value": { ++ "map": { ++ "data": { ++ "set": [ ++ [ ++ 1, ++ 1 ++ ], ++ [ ++ 4095, ++ 16533 ++ ] ++ ] ++ }, ++ "key": { ++ "payload": { ++ "field": "id", ++ "protocol": "vlan" ++ } ++ } ++ } ++ } ++ } ++ } ++] ++ ++# meta mark set vlan id map @map1 ++[ ++ { ++ "mangle": { ++ "key": { ++ "meta": { ++ "key": "mark" ++ } ++ }, ++ "value": { ++ "map": { ++ "data": "@map1", ++ "key": { ++ "payload": { ++ "field": "id", ++ "protocol": "vlan" ++ } ++ } ++ } ++ } ++ } ++ } ++] ++ diff --git a/0048-parser_bison-fix-objref-statement-corruption.patch b/0048-parser_bison-fix-objref-statement-corruption.patch new file mode 100644 index 0000000..06f9da0 --- /dev/null +++ b/0048-parser_bison-fix-objref-statement-corruption.patch @@ -0,0 +1,271 @@ +From fdf39fe2cf3f15cbea9c26b1c5951f207cda6ea8 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:41 +0200 +Subject: [PATCH] parser_bison: fix objref statement corruption + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 78dffb470fcf7b1c0b1b3d6f43fcc056c337a808 + +commit 78dffb470fcf7b1c0b1b3d6f43fcc056c337a808 +Author: Florian Westphal +Date: Fri Dec 8 19:41:39 2023 +0100 + + parser_bison: fix objref statement corruption + + Consider this: + + counter_stmt : counter_stmt_alloc + | counter_stmt_alloc counter_args + + counter_stmt_alloc : COUNTER { $$ = counter_stmt_alloc(&@$); } + | COUNTER NAME stmt_expr + { + $$ = objref_stmt_alloc(&@$); + $$->objref.type = NFT_OBJECT_COUNTER; + $$->objref.expr = $3; + } + ; + + counter_args : counter_arg { $$ = $0; } + | counter_args counter_arg + ; + + counter_arg : PACKETS NUM { $0->counter.packets = $2; } + + [..] + + This has 'counter_stmt_alloc' EITHER return counter or objref statement. + Both are the same structure but with different (union'd) trailer content. + + counter_stmt permits the 'packet' and 'byte' argument. + + But the 'counter_arg' directive only works with a statement + coming from counter_stmt_alloc(). + + afl++ came up with following input: + + table inet x { + chain y { + counter name ip saddr bytes 1.1.1. 1024 + } + } + + This clobbers $->objref.expr pointer, we then crash when + calling expr_evaluate() on it. + + Split the objref related statements into their own directive. + + After this, the input will fail with: + "syntax error, unexpected bytes, expecting newline or semicolon". + + Also split most of the other objref statements into their own blocks. + synproxy seems to have same problem, limit and quota appeared to be ok. + + v1 added objref_stmt to stateful_stmt list, this is wrong, we will + assert when generating the 'counter' statement. + Place it in the normal statement list so netlink_gen_stmt_stateful_assert + throws the expected parser error. + + Fixes: dccab4f646b4 ("parser_bison: consolidate stmt_expr rule") + Signed-off-by: Florian Westphal + +Signed-off-by: Phil Sutter +--- + src/parser_bison.y | 97 ++++++++++++------- + .../bogons/nft-f/counter_objref_crash | 5 + + .../nft-f/netlink_gen_stmt_stateful_assert | 6 ++ + 3 files changed, 71 insertions(+), 37 deletions(-) + create mode 100644 tests/shell/testcases/bogons/nft-f/counter_objref_crash + create mode 100644 tests/shell/testcases/bogons/nft-f/netlink_gen_stmt_stateful_assert + +diff --git a/src/parser_bison.y b/src/parser_bison.y +index 9386a9c..2c829f9 100644 +--- a/src/parser_bison.y ++++ b/src/parser_bison.y +@@ -746,6 +746,9 @@ int nft_lex(void *, void *, void *); + %destructor { stmt_free($$); } stmt match_stmt verdict_stmt set_elem_stmt + %type counter_stmt counter_stmt_alloc stateful_stmt last_stmt + %destructor { stmt_free($$); } counter_stmt counter_stmt_alloc stateful_stmt last_stmt ++%type objref_stmt objref_stmt_counter objref_stmt_limit objref_stmt_quota objref_stmt_ct objref_stmt_synproxy ++%destructor { stmt_free($$); } objref_stmt objref_stmt_counter objref_stmt_limit objref_stmt_quota objref_stmt_ct objref_stmt_synproxy ++ + %type payload_stmt + %destructor { stmt_free($$); } payload_stmt + %type ct_stmt +@@ -3060,6 +3063,60 @@ stateful_stmt_list : stateful_stmt + } + ; + ++objref_stmt_counter : COUNTER NAME stmt_expr close_scope_counter ++ { ++ $$ = objref_stmt_alloc(&@$); ++ $$->objref.type = NFT_OBJECT_COUNTER; ++ $$->objref.expr = $3; ++ } ++ ; ++ ++objref_stmt_limit : LIMIT NAME stmt_expr close_scope_limit ++ { ++ $$ = objref_stmt_alloc(&@$); ++ $$->objref.type = NFT_OBJECT_LIMIT; ++ $$->objref.expr = $3; ++ } ++ ; ++ ++objref_stmt_quota : QUOTA NAME stmt_expr close_scope_quota ++ { ++ $$ = objref_stmt_alloc(&@$); ++ $$->objref.type = NFT_OBJECT_QUOTA; ++ $$->objref.expr = $3; ++ } ++ ; ++ ++objref_stmt_synproxy : SYNPROXY NAME stmt_expr close_scope_synproxy ++ { ++ $$ = objref_stmt_alloc(&@$); ++ $$->objref.type = NFT_OBJECT_SYNPROXY; ++ $$->objref.expr = $3; ++ } ++ ; ++ ++objref_stmt_ct : CT TIMEOUT SET stmt_expr close_scope_ct ++ { ++ $$ = objref_stmt_alloc(&@$); ++ $$->objref.type = NFT_OBJECT_CT_TIMEOUT; ++ $$->objref.expr = $4; ++ ++ } ++ | CT EXPECTATION SET stmt_expr close_scope_ct ++ { ++ $$ = objref_stmt_alloc(&@$); ++ $$->objref.type = NFT_OBJECT_CT_EXPECT; ++ $$->objref.expr = $4; ++ } ++ ; ++ ++objref_stmt : objref_stmt_counter ++ | objref_stmt_limit ++ | objref_stmt_quota ++ | objref_stmt_synproxy ++ | objref_stmt_ct ++ ; ++ + stateful_stmt : counter_stmt close_scope_counter + | limit_stmt + | quota_stmt +@@ -3089,6 +3146,7 @@ stmt : verdict_stmt + | chain_stmt + | optstrip_stmt + | xt_stmt close_scope_xt ++ | objref_stmt + ; + + xt_stmt : XT STRING string +@@ -3178,12 +3236,6 @@ counter_stmt_alloc : COUNTER + { + $$ = counter_stmt_alloc(&@$); + } +- | COUNTER NAME stmt_expr +- { +- $$ = objref_stmt_alloc(&@$); +- $$->objref.type = NFT_OBJECT_COUNTER; +- $$->objref.expr = $3; +- } + ; + + counter_args : counter_arg +@@ -3195,10 +3247,12 @@ counter_args : counter_arg + + counter_arg : PACKETS NUM + { ++ assert($0->ops->type == STMT_COUNTER); + $0->counter.packets = $2; + } + | BYTES NUM + { ++ assert($0->ops->type == STMT_COUNTER); + $0->counter.bytes = $2; + } + ; +@@ -3479,12 +3533,6 @@ limit_stmt : LIMIT RATE limit_mode limit_rate_pkts limit_burst_pkts close_scope + $$->limit.type = NFT_LIMIT_PKT_BYTES; + $$->limit.flags = $3; + } +- | LIMIT NAME stmt_expr close_scope_limit +- { +- $$ = objref_stmt_alloc(&@$); +- $$->objref.type = NFT_OBJECT_LIMIT; +- $$->objref.expr = $3; +- } + ; + + quota_mode : OVER { $$ = NFT_QUOTA_F_INV; } +@@ -3528,12 +3576,6 @@ quota_stmt : QUOTA quota_mode NUM quota_unit quota_used close_scope_quota + $$->quota.used = $5; + $$->quota.flags = $2; + } +- | QUOTA NAME stmt_expr close_scope_quota +- { +- $$ = objref_stmt_alloc(&@$); +- $$->objref.type = NFT_OBJECT_QUOTA; +- $$->objref.expr = $3; +- } + ; + + limit_mode : OVER { $$ = NFT_LIMIT_F_INV; } +@@ -3724,12 +3766,6 @@ synproxy_stmt_alloc : SYNPROXY + { + $$ = synproxy_stmt_alloc(&@$); + } +- | SYNPROXY NAME stmt_expr +- { +- $$ = objref_stmt_alloc(&@$); +- $$->objref.type = NFT_OBJECT_SYNPROXY; +- $$->objref.expr = $3; +- } + ; + + synproxy_args : synproxy_arg +@@ -5542,19 +5578,6 @@ ct_stmt : CT ct_key SET stmt_expr close_scope_ct + break; + } + } +- | CT TIMEOUT SET stmt_expr close_scope_ct +- { +- $$ = objref_stmt_alloc(&@$); +- $$->objref.type = NFT_OBJECT_CT_TIMEOUT; +- $$->objref.expr = $4; +- +- } +- | CT EXPECTATION SET stmt_expr close_scope_ct +- { +- $$ = objref_stmt_alloc(&@$); +- $$->objref.type = NFT_OBJECT_CT_EXPECT; +- $$->objref.expr = $4; +- } + | CT ct_dir ct_key_dir_optional SET stmt_expr close_scope_ct + { + $$ = ct_stmt_alloc(&@$, $3, $2, $5); +diff --git a/tests/shell/testcases/bogons/nft-f/counter_objref_crash b/tests/shell/testcases/bogons/nft-f/counter_objref_crash +new file mode 100644 +index 0000000..3a4b981 +--- /dev/null ++++ b/tests/shell/testcases/bogons/nft-f/counter_objref_crash +@@ -0,0 +1,5 @@ ++table inet x { ++ chain y { ++ counter name ip saddr bytes 1.1.1. 1024 ++ } ++} +diff --git a/tests/shell/testcases/bogons/nft-f/netlink_gen_stmt_stateful_assert b/tests/shell/testcases/bogons/nft-f/netlink_gen_stmt_stateful_assert +new file mode 100644 +index 0000000..547b937 +--- /dev/null ++++ b/tests/shell/testcases/bogons/nft-f/netlink_gen_stmt_stateful_assert +@@ -0,0 +1,6 @@ ++table ip x { ++ map sctm_o1 { ++ type mark : counter ++ counter name meta mark ++ } ++} diff --git a/0049-evaluate-fix-bogus-assertion-failure-with-boolean-da.patch b/0049-evaluate-fix-bogus-assertion-failure-with-boolean-da.patch new file mode 100644 index 0000000..80b1d34 --- /dev/null +++ b/0049-evaluate-fix-bogus-assertion-failure-with-boolean-da.patch @@ -0,0 +1,128 @@ +From 62be3f747b22309adff113b95d6cadc2d7a0a470 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:41 +0200 +Subject: [PATCH] evaluate: fix bogus assertion failure with boolean datatype + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 567937b5560fbcc7f6b74fb43c52e1cab2ac425a + +commit 567937b5560fbcc7f6b74fb43c52e1cab2ac425a +Author: Florian Westphal +Date: Fri Dec 8 19:38:33 2023 +0100 + + evaluate: fix bogus assertion failure with boolean datatype + + The assertion is too strict, as found by afl++: + + typeof iifname . ip saddr . meta ipsec + elements = { "eth0" . 10.1.1.2 . 1 } + + meta ipsec is boolean (1 bit), but datasize of 1 is set at 8 bit. + + Fixes: 22b750aa6dc9 ("src: allow use of base integer types as set keys in concatenations") + Signed-off-by: Florian Westphal + +Signed-off-by: Phil Sutter +--- + src/evaluate.c | 7 ++++--- + .../testcases/sets/dumps/typeof_sets_0.nft | 9 +++++++++ + tests/shell/testcases/sets/typeof_sets_0 | 17 +++++++++++++++++ + 3 files changed, 30 insertions(+), 3 deletions(-) + +diff --git a/src/evaluate.c b/src/evaluate.c +index cfdc6c0..fa3512b 100644 +--- a/src/evaluate.c ++++ b/src/evaluate.c +@@ -4618,14 +4618,15 @@ static int set_expr_evaluate_concat(struct eval_ctx *ctx, struct expr **expr) + "expressions", + i->dtype->name); + +- if (i->dtype->size) +- assert(i->len == i->dtype->size); +- + flags &= i->flags; + + ntype = concat_subtype_add(ntype, i->dtype->type); + + dsize_bytes = div_round_up(i->len, BITS_PER_BYTE); ++ ++ if (i->dtype->size) ++ assert(dsize_bytes == div_round_up(i->dtype->size, BITS_PER_BYTE)); ++ + (*expr)->field_len[(*expr)->field_count++] = dsize_bytes; + size += netlink_padded_len(i->len); + } +diff --git a/tests/shell/testcases/sets/dumps/typeof_sets_0.nft b/tests/shell/testcases/sets/dumps/typeof_sets_0.nft +index 6f5b83a..63fc5b1 100644 +--- a/tests/shell/testcases/sets/dumps/typeof_sets_0.nft ++++ b/tests/shell/testcases/sets/dumps/typeof_sets_0.nft +@@ -55,6 +55,11 @@ table inet t { + elements = { 3567 . 1.2.3.4 } + } + ++ set s12 { ++ typeof iifname . ip saddr . meta ipsec ++ elements = { "eth0" . 10.1.1.2 . exists } ++ } ++ + chain c1 { + osf name @s1 accept + } +@@ -94,4 +99,8 @@ table inet t { + chain c11 { + vlan id . ip saddr @s11 accept + } ++ ++ chain c12 { ++ iifname . ip saddr . meta ipsec @s12 accept ++ } + } +diff --git a/tests/shell/testcases/sets/typeof_sets_0 b/tests/shell/testcases/sets/typeof_sets_0 +index 35c572c..943c9c2 100755 +--- a/tests/shell/testcases/sets/typeof_sets_0 ++++ b/tests/shell/testcases/sets/typeof_sets_0 +@@ -92,6 +92,10 @@ INPUT="table inet t {$INPUT_OSF_SET + typeof vlan id . ip saddr + elements = { 3567 . 1.2.3.4 } + } ++ set s12 { ++ typeof meta iifname . ip saddr . meta ipsec ++ elements = { \"eth0\" . 10.1.1.2 . 1 } ++ } + $INPUT_OSF_CHAIN + chain c2 { + ether type vlan vlan id @s2 accept +@@ -124,6 +128,10 @@ $INPUT_SCTP_CHAIN + chain c11 { + ether type vlan vlan id . ip saddr @s11 accept + } ++ ++ chain c12 { ++ meta iifname . ip saddr . meta ipsec @s12 accept ++ } + }" + + EXPECTED="table inet t {$INPUT_OSF_SET +@@ -177,6 +185,11 @@ EXPECTED="table inet t {$INPUT_OSF_SET + typeof vlan id . ip saddr + elements = { 3567 . 1.2.3.4 } + } ++ ++ set s12 { ++ typeof iifname . ip saddr . meta ipsec ++ elements = { \"eth0\" . 10.1.1.2 . exists } ++ } + $INPUT_OSF_CHAIN + chain c2 { + vlan id @s2 accept +@@ -209,6 +222,10 @@ $INPUT_SCTP_CHAIN + chain c11 { + vlan id . ip saddr @s11 accept + } ++ ++ chain c12 { ++ iifname . ip saddr . meta ipsec @s12 accept ++ } + }" + + diff --git a/0050-parser_bison-close-chain-scope-before-chain-release.patch b/0050-parser_bison-close-chain-scope-before-chain-release.patch new file mode 100644 index 0000000..18166cc --- /dev/null +++ b/0050-parser_bison-close-chain-scope-before-chain-release.patch @@ -0,0 +1,58 @@ +From fdb03b033c3efcb28b4c51c469d3e93559d7bdb8 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:41 +0200 +Subject: [PATCH] parser_bison: close chain scope before chain release + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 28b3bbec5a97aaa57f7138fee8b2f22ff0e5b960 + +commit 28b3bbec5a97aaa57f7138fee8b2f22ff0e5b960 +Author: Florian Westphal +Date: Wed Dec 13 11:09:58 2023 +0100 + + parser_bison: close chain scope before chain release + + cmd_alloc() will free the chain, so we must close the scope opened + in chain_block_alloc beforehand. + + The included test file will cause a use-after-free because nft attempts + to search for an identifier in a scope that has been freed: + + AddressSanitizer: heap-use-after-free on address 0x618000000368 at pc 0x7f1cbc0e6959 bp 0x7ffd3ccb7850 sp 0x7ffd3ccb7840 + #0 0x7f1cbc0e6958 in symbol_lookup src/rule.c:629 + #1 0x7f1cbc0e66a1 in symbol_get src/rule.c:588 + #2 0x7f1cbc120d67 in nft_parse src/parser_bison.y:4325 + + Fixes: a66b5ad9540d ("src: allow for updating devices on existing netdev chain") + Signed-off-by: Florian Westphal + +Signed-off-by: Phil Sutter +--- + src/parser_bison.y | 1 + + .../testcases/bogons/nft-f/use_after_free_on_chain_removal | 5 +++++ + 2 files changed, 6 insertions(+) + create mode 100644 tests/shell/testcases/bogons/nft-f/use_after_free_on_chain_removal + +diff --git a/src/parser_bison.y b/src/parser_bison.y +index 2c829f9..83302fd 100644 +--- a/src/parser_bison.y ++++ b/src/parser_bison.y +@@ -1397,6 +1397,7 @@ delete_cmd : TABLE table_or_id_spec + { + $5->location = @5; + handle_merge(&$3->handle, &$2); ++ close_scope(state); + $$ = cmd_alloc(CMD_DELETE, CMD_OBJ_CHAIN, &$2, &@$, $5); + } + | RULE ruleid_spec +diff --git a/tests/shell/testcases/bogons/nft-f/use_after_free_on_chain_removal b/tests/shell/testcases/bogons/nft-f/use_after_free_on_chain_removal +new file mode 100644 +index 0000000..bb9632b +--- /dev/null ++++ b/tests/shell/testcases/bogons/nft-f/use_after_free_on_chain_removal +@@ -0,0 +1,5 @@ ++delete chain d iUi { ++}} ++delete chain d hUi { ++delete chain o ++c b icmpv6 id$i diff --git a/0051-meta-fix-tc-classid-parsing-out-of-bounds-access.patch b/0051-meta-fix-tc-classid-parsing-out-of-bounds-access.patch new file mode 100644 index 0000000..827fee9 --- /dev/null +++ b/0051-meta-fix-tc-classid-parsing-out-of-bounds-access.patch @@ -0,0 +1,112 @@ +From ae85b206060f420d4eb8f2ba0a61bb6c6813b908 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:41 +0200 +Subject: [PATCH] meta: fix tc classid parsing out-of-bounds access + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 7008b1200fb4988b7cd7ee1c5399cae071688d50 + +commit 7008b1200fb4988b7cd7ee1c5399cae071688d50 +Author: Florian Westphal +Date: Wed Dec 13 17:37:11 2023 +0100 + + meta: fix tc classid parsing out-of-bounds access + + AddressSanitizer: heap-buffer-overflow on address 0x6020000003af ... + #0 0x7f9a83cbb402 in tchandle_type_parse src/meta.c:89 + #1 0x7f9a83c6753f in symbol_parse src/datatype.c:138 + + strlen() - 1 can underflow if length was 0. + + Simplify the function, there is no need to duplicate the string + while scanning it. + + Expect the first strtol to stop at ':', scan for the minor number next. + The second scan is required to stop at '\0'. + + Fixes: 6f2eb8548e0d ("src: meta priority support using tc classid") + Signed-off-by: Florian Westphal + +Signed-off-by: Phil Sutter +--- + src/meta.c | 29 ++++++------------- + .../nft-f/tchandle_type_parse_heap_overflow | 6 ++++ + 2 files changed, 15 insertions(+), 20 deletions(-) + create mode 100644 tests/shell/testcases/bogons/nft-f/tchandle_type_parse_heap_overflow + +diff --git a/src/meta.c b/src/meta.c +index 4e55e00..6f76f00 100644 +--- a/src/meta.c ++++ b/src/meta.c +@@ -62,50 +62,39 @@ static struct error_record *tchandle_type_parse(struct parse_ctx *ctx, + struct expr **res) + { + uint32_t handle; +- char *str = NULL; + + if (strcmp(sym->identifier, "root") == 0) + handle = TC_H_ROOT; + else if (strcmp(sym->identifier, "none") == 0) + handle = TC_H_UNSPEC; + else if (strchr(sym->identifier, ':')) { ++ char *colon, *end; + uint32_t tmp; +- char *colon; +- +- str = xstrdup(sym->identifier); +- +- colon = strchr(str, ':'); +- if (!colon) +- goto err; +- +- *colon = '\0'; + + errno = 0; +- tmp = strtoull(str, NULL, 16); +- if (errno != 0) ++ tmp = strtoul(sym->identifier, &colon, 16); ++ if (errno != 0 || sym->identifier == colon) + goto err; + +- handle = (tmp << 16); +- if (str[strlen(str) - 1] == ':') +- goto out; ++ if (*colon != ':') ++ goto err; + ++ handle = tmp << 16; + errno = 0; +- tmp = strtoull(colon + 1, NULL, 16); +- if (errno != 0) ++ tmp = strtoul(colon + 1, &end, 16); ++ if (errno != 0 || *end) + goto err; + + handle |= tmp; + } else { + handle = strtoull(sym->identifier, NULL, 0); + } +-out: +- free(str); ++ + *res = constant_expr_alloc(&sym->location, sym->dtype, + BYTEORDER_HOST_ENDIAN, + sizeof(handle) * BITS_PER_BYTE, &handle); + return NULL; + err: +- free(str); + return error(&sym->location, "Could not parse %s", sym->dtype->desc); + } + +diff --git a/tests/shell/testcases/bogons/nft-f/tchandle_type_parse_heap_overflow b/tests/shell/testcases/bogons/nft-f/tchandle_type_parse_heap_overflow +new file mode 100644 +index 0000000..ea7186b +--- /dev/null ++++ b/tests/shell/testcases/bogons/nft-f/tchandle_type_parse_heap_overflow +@@ -0,0 +1,6 @@ ++table t { ++map m { ++ type ipv4_addr : classid ++ elements = { 1.1.26.3 : ::a } ++} ++} diff --git a/0052-evaluate-exthdr-statement-arg-must-be-not-be-a-range.patch b/0052-evaluate-exthdr-statement-arg-must-be-not-be-a-range.patch new file mode 100644 index 0000000..880db3f --- /dev/null +++ b/0052-evaluate-exthdr-statement-arg-must-be-not-be-a-range.patch @@ -0,0 +1,69 @@ +From 9f755eed0849227f88d69b5efeb2809a8fc311a7 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:41 +0200 +Subject: [PATCH] evaluate: exthdr: statement arg must be not be a range + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 8eeedce89d8bf0ad58da398782c2ca8a91d83a32 + +commit 8eeedce89d8bf0ad58da398782c2ca8a91d83a32 +Author: Florian Westphal +Date: Thu Dec 14 17:56:59 2023 +0100 + + evaluate: exthdr: statement arg must be not be a range + + Else we get: + BUG: unknown expression type range + nft: src/netlink_linearize.c:909: netlink_gen_expr: Assertion `0' failed. + + Signed-off-by: Florian Westphal + +Signed-off-by: Phil Sutter +--- + src/evaluate.c | 19 ++++++++++++++++--- + .../bogons/nft-f/exthdr_with_range_bug | 1 + + 2 files changed, 17 insertions(+), 3 deletions(-) + create mode 100644 tests/shell/testcases/bogons/nft-f/exthdr_with_range_bug + +diff --git a/src/evaluate.c b/src/evaluate.c +index fa3512b..78a5d10 100644 +--- a/src/evaluate.c ++++ b/src/evaluate.c +@@ -2964,14 +2964,27 @@ static bool stmt_evaluate_payload_need_csum(const struct expr *payload) + static int stmt_evaluate_exthdr(struct eval_ctx *ctx, struct stmt *stmt) + { + struct expr *exthdr; ++ int ret; + + if (__expr_evaluate_exthdr(ctx, &stmt->exthdr.expr) < 0) + return -1; + + exthdr = stmt->exthdr.expr; +- return stmt_evaluate_arg(ctx, stmt, exthdr->dtype, exthdr->len, +- BYTEORDER_BIG_ENDIAN, +- &stmt->exthdr.val); ++ ret = stmt_evaluate_arg(ctx, stmt, exthdr->dtype, exthdr->len, ++ BYTEORDER_BIG_ENDIAN, ++ &stmt->exthdr.val); ++ if (ret < 0) ++ return ret; ++ ++ switch (stmt->exthdr.val->etype) { ++ case EXPR_RANGE: ++ return expr_error(ctx->msgs, stmt->exthdr.val, ++ "cannot be a range"); ++ default: ++ break; ++ } ++ ++ return 0; + } + + static int stmt_evaluate_payload(struct eval_ctx *ctx, struct stmt *stmt) +diff --git a/tests/shell/testcases/bogons/nft-f/exthdr_with_range_bug b/tests/shell/testcases/bogons/nft-f/exthdr_with_range_bug +new file mode 100644 +index 0000000..e307e7c +--- /dev/null ++++ b/tests/shell/testcases/bogons/nft-f/exthdr_with_range_bug +@@ -0,0 +1 @@ ++add rule t c ip option ra set 0-1 diff --git a/0053-src-reject-large-raw-payload-and-concat-expressions.patch b/0053-src-reject-large-raw-payload-and-concat-expressions.patch new file mode 100644 index 0000000..7bd8694 --- /dev/null +++ b/0053-src-reject-large-raw-payload-and-concat-expressions.patch @@ -0,0 +1,121 @@ +From 15e1ec4b465ba529675998d6da5d90ba2020f087 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:41 +0200 +Subject: [PATCH] src: reject large raw payload and concat expressions + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit ef10d65db278d77208e960d210a1f4f532ebb552 + +commit ef10d65db278d77208e960d210a1f4f532ebb552 +Author: Florian Westphal +Date: Tue Dec 12 19:13:14 2023 +0100 + + src: reject large raw payload and concat expressions + + The kernel will reject this too, but unfortunately nft may try + to cram the data into the underlying libnftnl expr. + + This causes heap corruption or + BUG: nld buffer overflow: want to copy 132, max 64 + + After: + + Error: Concatenation of size 544 exceeds maximum size of 512 + udp length . @th,0,512 . @th,512,512 { 47-63 . 0xe373135363130 . 0x33131303735353203 } + ^^^^^^^^^ + + resp. same warning for an over-sized raw expression. + + Signed-off-by: Florian Westphal + +Signed-off-by: Phil Sutter +--- + include/expression.h | 3 +++ + src/evaluate.c | 8 ++++++++ + src/parser_bison.y | 7 +++++++ + .../bogons/nft-f/stack_overflow_via_large_concat_expr | 5 +++++ + .../bogons/nft-f/stack_overflow_via_large_raw_expr | 5 +++++ + 5 files changed, 28 insertions(+) + create mode 100644 tests/shell/testcases/bogons/nft-f/stack_overflow_via_large_concat_expr + create mode 100644 tests/shell/testcases/bogons/nft-f/stack_overflow_via_large_raw_expr + +diff --git a/include/expression.h b/include/expression.h +index aede223..809089c 100644 +--- a/include/expression.h ++++ b/include/expression.h +@@ -11,6 +11,9 @@ + #include + #include + ++#define NFT_MAX_EXPR_LEN_BYTES (NFT_REG32_COUNT * sizeof(uint32_t)) ++#define NFT_MAX_EXPR_LEN_BITS (NFT_MAX_EXPR_LEN_BYTES * BITS_PER_BYTE) ++ + /** + * enum expr_types + * +diff --git a/src/evaluate.c b/src/evaluate.c +index 78a5d10..1a8c815 100644 +--- a/src/evaluate.c ++++ b/src/evaluate.c +@@ -1569,6 +1569,10 @@ static int expr_evaluate_concat(struct eval_ctx *ctx, struct expr **expr) + } + + ctx->inner_desc = NULL; ++ ++ if (size > NFT_MAX_EXPR_LEN_BITS) ++ return expr_error(ctx->msgs, i, "Concatenation of size %u exceeds maximum size of %u", ++ size, NFT_MAX_EXPR_LEN_BITS); + } + + (*expr)->flags |= flags; +@@ -4642,6 +4646,10 @@ static int set_expr_evaluate_concat(struct eval_ctx *ctx, struct expr **expr) + + (*expr)->field_len[(*expr)->field_count++] = dsize_bytes; + size += netlink_padded_len(i->len); ++ ++ if (size > NFT_MAX_EXPR_LEN_BITS) ++ return expr_error(ctx->msgs, i, "Concatenation of size %u exceeds maximum size of %u", ++ size, NFT_MAX_EXPR_LEN_BITS); + } + + (*expr)->flags |= flags; +diff --git a/src/parser_bison.y b/src/parser_bison.y +index 83302fd..fa59207 100644 +--- a/src/parser_bison.y ++++ b/src/parser_bison.y +@@ -5620,6 +5620,13 @@ payload_expr : payload_raw_expr + + payload_raw_expr : AT payload_base_spec COMMA NUM COMMA NUM close_scope_at + { ++ if ($6 > NFT_MAX_EXPR_LEN_BITS) { ++ erec_queue(error(&@1, "raw payload length %u exceeds upper limit of %u", ++ $6, NFT_MAX_EXPR_LEN_BITS), ++ state->msgs); ++ YYERROR; ++ } ++ + $$ = payload_expr_alloc(&@$, NULL, 0); + payload_init_raw($$, $2, $4, $6); + $$->byteorder = BYTEORDER_BIG_ENDIAN; +diff --git a/tests/shell/testcases/bogons/nft-f/stack_overflow_via_large_concat_expr b/tests/shell/testcases/bogons/nft-f/stack_overflow_via_large_concat_expr +new file mode 100644 +index 0000000..8b0d274 +--- /dev/null ++++ b/tests/shell/testcases/bogons/nft-f/stack_overflow_via_large_concat_expr +@@ -0,0 +1,5 @@ ++table t { ++ chain c { ++ udp length . @th,0,512 . @th,512,512 { 47-63 . 0xe373135363130 . 0x33131303735353203 } ++ } ++} +diff --git a/tests/shell/testcases/bogons/nft-f/stack_overflow_via_large_raw_expr b/tests/shell/testcases/bogons/nft-f/stack_overflow_via_large_raw_expr +new file mode 100644 +index 0000000..66bd6bf +--- /dev/null ++++ b/tests/shell/testcases/bogons/nft-f/stack_overflow_via_large_raw_expr +@@ -0,0 +1,5 @@ ++table t { ++ chain c { ++ @th,160,1272 gt 0 ++ } ++} diff --git a/0054-evaluate-fix-stack-overflow-with-huge-priority-strin.patch b/0054-evaluate-fix-stack-overflow-with-huge-priority-strin.patch new file mode 100644 index 0000000..f16d2b1 --- /dev/null +++ b/0054-evaluate-fix-stack-overflow-with-huge-priority-strin.patch @@ -0,0 +1,51 @@ +From 23a5147a32733f75548526e9b8769cd1157b34f8 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:41 +0200 +Subject: [PATCH] evaluate: fix stack overflow with huge priority string + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit b626c86abaf294fcf1ec788f722071dc90da68c4 + +commit b626c86abaf294fcf1ec788f722071dc90da68c4 +Author: Florian Westphal +Date: Fri Dec 15 10:19:02 2023 +0100 + + evaluate: fix stack overflow with huge priority string + + Alternative would be to refactor this and move this into the parsers + (bison, json) instead of this hidden re-parsing. + + Fixes: 627c451b2351 ("src: allow variables in the chain priority specification") + Signed-off-by: Florian Westphal + +Signed-off-by: Phil Sutter +--- + src/evaluate.c | 2 +- + tests/shell/testcases/bogons/nft-f/huge_chain_prio | 5 +++++ + 2 files changed, 6 insertions(+), 1 deletion(-) + create mode 100644 tests/shell/testcases/bogons/nft-f/huge_chain_prio + +diff --git a/src/evaluate.c b/src/evaluate.c +index 1a8c815..c655e3a 100644 +--- a/src/evaluate.c ++++ b/src/evaluate.c +@@ -4820,7 +4820,7 @@ static bool evaluate_priority(struct eval_ctx *ctx, struct prio_spec *prio, + NFT_NAME_MAXLEN); + loc = prio->expr->location; + +- if (sscanf(prio_str, "%s %c %d", prio_fst, &op, &prio_snd) < 3) { ++ if (sscanf(prio_str, "%255s %c %d", prio_fst, &op, &prio_snd) < 3) { + priority = std_prio_lookup(prio_str, family, hook); + if (priority == NF_IP_PRI_LAST) + return false; +diff --git a/tests/shell/testcases/bogons/nft-f/huge_chain_prio b/tests/shell/testcases/bogons/nft-f/huge_chain_prio +new file mode 100644 +index 0000000..41f8061 +--- /dev/null ++++ b/tests/shell/testcases/bogons/nft-f/huge_chain_prio +@@ -0,0 +1,5 @@ ++table t { ++ chain c { ++ type filter hook input priority srcnDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD#DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD; policy accept; ++ } ++} diff --git a/0055-tests-shell-add-test-to-cover-payload-transport-matc.patch b/0055-tests-shell-add-test-to-cover-payload-transport-matc.patch new file mode 100644 index 0000000..8e42c98 --- /dev/null +++ b/0055-tests-shell-add-test-to-cover-payload-transport-matc.patch @@ -0,0 +1,220 @@ +From dcd92e16a863ba062ff9792dd304b0eb958fb60c Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:41 +0200 +Subject: [PATCH] tests: shell: add test to cover payload transport match and + mangle + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 84da729e067a68bd50be8c0791d16901360be5ad + +commit 84da729e067a68bd50be8c0791d16901360be5ad +Author: Pablo Neira Ayuso +Date: Sun Dec 17 00:15:08 2023 +0100 + + tests: shell: add test to cover payload transport match and mangle + + Exercise payload transport match and mangle for inet, bridge and netdev + families with IPv4 and IPv6 packets. + + To cover kernel patch ("netfilter: nf_tables: set transport offset from + mac header for netdev/egress"). + + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + .../testcases/packetpath/dumps/payload.nodump | 0 + tests/shell/testcases/packetpath/payload | 180 ++++++++++++++++++ + 2 files changed, 180 insertions(+) + create mode 100644 tests/shell/testcases/packetpath/dumps/payload.nodump + create mode 100755 tests/shell/testcases/packetpath/payload + +diff --git a/tests/shell/testcases/packetpath/dumps/payload.nodump b/tests/shell/testcases/packetpath/dumps/payload.nodump +new file mode 100644 +index 0000000..e69de29 +diff --git a/tests/shell/testcases/packetpath/payload b/tests/shell/testcases/packetpath/payload +new file mode 100755 +index 0000000..1a89d85 +--- /dev/null ++++ b/tests/shell/testcases/packetpath/payload +@@ -0,0 +1,180 @@ ++#!/bin/bash ++ ++rnd=$(mktemp -u XXXXXXXX) ++ns1="nft1payload-$rnd" ++ns2="nft2payload-$rnd" ++ ++cleanup() ++{ ++ ip netns del "$ns1" ++ ip netns del "$ns2" ++} ++ ++trap cleanup EXIT ++ ++run_test() ++{ ++ ns1_addr=$2 ++ ns2_addr=$3 ++ cidr=$4 ++ ++ # socat needs square brackets, ie. [abcd::2] ++ if [ $1 -eq 6 ]; then ++ nsx1_addr="["$ns1_addr"]" ++ nsx2_addr="["$ns2_addr"]" ++ else ++ nsx1_addr="$ns1_addr" ++ nsx2_addr="$ns2_addr" ++ fi ++ ++ ip netns add "$ns1" || exit 111 ++ ip netns add "$ns2" || exit 111 ++ ++ ip -net "$ns1" link set lo up ++ ip -net "$ns2" link set lo up ++ ++ ip link add veth0 netns $ns1 type veth peer name veth0 netns $ns2 ++ ++ ip -net "$ns1" link set veth0 up ++ ip -net "$ns2" link set veth0 up ++ ip -net "$ns1" addr add $ns1_addr/$cidr dev veth0 ++ ip -net "$ns2" addr add $ns2_addr/$cidr dev veth0 ++ ++RULESET="table netdev payload_netdev { ++ counter ingress {} ++ counter egress {} ++ counter mangle_ingress {} ++ counter mangle_egress {} ++ counter mangle_ingress_match {} ++ counter mangle_egress_match {} ++ ++ chain ingress { ++ type filter hook ingress device veth0 priority 0; ++ tcp dport 7777 counter name ingress ++ tcp dport 7778 tcp dport set 7779 counter name mangle_ingress ++ tcp dport 7779 counter name mangle_ingress_match ++ } ++ ++ chain egress { ++ type filter hook egress device veth0 priority 0; ++ tcp dport 8887 counter name egress ++ tcp dport 8888 tcp dport set 8889 counter name mangle_egress ++ tcp dport 8889 counter name mangle_egress_match ++ } ++} ++ ++table inet payload_inet { ++ counter input {} ++ counter output {} ++ counter mangle_input {} ++ counter mangle_output {} ++ counter mangle_input_match {} ++ counter mangle_output_match {} ++ ++ chain in { ++ type filter hook input priority 0; ++ tcp dport 7770 counter name input ++ tcp dport 7771 tcp dport set 7772 counter name mangle_input ++ tcp dport 7772 counter name mangle_input_match ++ } ++ ++ chain out { ++ type filter hook output priority 0; ++ tcp dport 8880 counter name output ++ tcp dport 8881 tcp dport set 8882 counter name mangle_output ++ tcp dport 8882 counter name mangle_output_match ++ } ++}" ++ ++ ip netns exec "$ns1" $NFT -f - <<< "$RULESET" || exit 1 ++ ++ ip netns exec "$ns1" socat -u STDIN TCP:$nsx2_addr:8887,connect-timeout=2 < /dev/null > /dev/null ++ ip netns exec "$ns1" socat -u STDIN TCP:$nsx2_addr:8888,connect-timeout=2 < /dev/null > /dev/null ++ ++ ip netns exec "$ns1" socat -u STDIN TCP:$nsx2_addr:8880,connect-timeout=2 < /dev/null > /dev/null ++ ip netns exec "$ns1" socat -u STDIN TCP:$nsx2_addr:8881,connect-timeout=2 < /dev/null > /dev/null ++ ++ ip netns exec "$ns2" socat -u STDIN TCP:$nsx1_addr:7777,connect-timeout=2 < /dev/null > /dev/null ++ ip netns exec "$ns2" socat -u STDIN TCP:$nsx1_addr:7778,connect-timeout=2 < /dev/null > /dev/null ++ ++ ip netns exec "$ns2" socat -u STDIN TCP:$nsx1_addr:7770,connect-timeout=2 < /dev/null > /dev/null ++ ip netns exec "$ns2" socat -u STDIN TCP:$nsx1_addr:7771,connect-timeout=2 < /dev/null > /dev/null ++ ++ ip netns exec "$ns1" $NFT list ruleset ++ ++ ip netns exec "$ns1" nft list counter netdev payload_netdev ingress | grep -v "packets 0" > /dev/null || exit 1 ++ ip netns exec "$ns1" nft list counter netdev payload_netdev mangle_ingress | grep -v "packets 0" > /dev/null || exit 1 ++ ip netns exec "$ns1" nft list counter netdev payload_netdev mangle_ingress_match | grep -v "packets 0" > /dev/null || exit 1 ++ ip netns exec "$ns1" nft list counter netdev payload_netdev egress | grep -v "packets 0" > /dev/null || exit 1 ++ ip netns exec "$ns1" nft list counter netdev payload_netdev mangle_egress | grep -v "packets 0" > /dev/null || exit 1 ++ ip netns exec "$ns1" nft list counter netdev payload_netdev mangle_egress_match | grep -v "packets 0" > /dev/null || exit 1 ++ ++ ip netns exec "$ns1" nft list counter inet payload_inet input | grep -v "packets 0" > /dev/null || exit 1 ++ ip netns exec "$ns1" nft list counter inet payload_inet mangle_input | grep -v "packets 0" > /dev/null || exit 1 ++ ip netns exec "$ns1" nft list counter inet payload_inet mangle_input_match | grep -v "packets 0" > /dev/null || exit 1 ++ ip netns exec "$ns1" nft list counter inet payload_inet output | grep -v "packets 0" > /dev/null || exit 1 ++ ip netns exec "$ns1" nft list counter inet payload_inet mangle_output | grep -v "packets 0" > /dev/null || exit 1 ++ ip netns exec "$ns1" nft list counter inet payload_inet mangle_output_match | grep -v "packets 0" > /dev/null || exit 1 ++ ++ # ++ # ... next stage ++ # ++ ++ ip netns exec "$ns1" $NFT flush ruleset ++ ++ # ++ # bridge ++ # ++ ++ ip -net "$ns1" addr del $ns1_addr/$cidr dev veth0 ++ ++ ip -net "$ns1" link add name br0 type bridge ++ ip -net "$ns1" link set veth0 master br0 ++ ip -net "$ns1" addr add $ns1_addr/$cidr dev br0 ++ ip -net "$ns1" link set up dev br0 ++ ++RULESET="table bridge payload_bridge { ++ counter input {} ++ counter output {} ++ counter mangle_input {} ++ counter mangle_output {} ++ counter mangle_input_match {} ++ counter mangle_output_match {} ++ ++ chain in { ++ type filter hook input priority 0; ++ tcp dport 7770 counter name input ++ tcp dport 7771 tcp dport set 7772 counter name mangle_input ++ tcp dport 7772 counter name mangle_input_match ++ } ++ ++ chain out { ++ type filter hook output priority 0; ++ tcp dport 8880 counter name output ++ tcp dport 8881 tcp dport set 8882 counter name mangle_output ++ tcp dport 8882 counter name mangle_output_match ++ } ++}" ++ ++ ip netns exec "$ns1" $NFT -f - <<< "$RULESET" || exit 1 ++ ++ ip netns exec "$ns1" socat -u STDIN TCP:$nsx2_addr:8880,connect-timeout=2 < /dev/null > /dev/null ++ ip netns exec "$ns1" socat -u STDIN TCP:$nsx2_addr:8881,connect-timeout=2 < /dev/null > /dev/null ++ ++ ip netns exec "$ns2" socat -u STDIN TCP:$nsx1_addr:7770,connect-timeout=2 < /dev/null > /dev/null ++ ip netns exec "$ns2" socat -u STDIN TCP:$nsx1_addr:7771,connect-timeout=2 < /dev/null > /dev/null ++ ++ ip netns exec "$ns1" $NFT list ruleset ++ ++ ip netns exec "$ns1" nft list counter bridge payload_bridge input | grep -v "packets 0" > /dev/null || exit 1 ++ ip netns exec "$ns1" nft list counter bridge payload_bridge mangle_input | grep -v "packets 0" > /dev/null || exit 1 ++ ip netns exec "$ns1" nft list counter bridge payload_bridge mangle_input_match | grep -v "packets 0" > /dev/null || exit 1 ++ ip netns exec "$ns1" nft list counter bridge payload_bridge output | grep -v "packets 0" > /dev/null || exit 1 ++ ip netns exec "$ns1" nft list counter bridge payload_bridge mangle_output | grep -v "packets 0" > /dev/null || exit 1 ++ ip netns exec "$ns1" nft list counter bridge payload_bridge mangle_output_match | grep -v "packets 0" > /dev/null || exit 1 ++} ++ ++run_test "4" "10.141.10.2" "10.141.10.3" "24" ++cleanup ++run_test 6 "abcd::2" "abcd::3" "64" ++# trap calls cleanup diff --git a/0056-parser_bison-error-out-on-duplicated-type-typeof-ele.patch b/0056-parser_bison-error-out-on-duplicated-type-typeof-ele.patch new file mode 100644 index 0000000..9f2bc47 --- /dev/null +++ b/0056-parser_bison-error-out-on-duplicated-type-typeof-ele.patch @@ -0,0 +1,120 @@ +From e1c01c2858d276e16b8fea5c42014799d00258c9 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:41 +0200 +Subject: [PATCH] parser_bison: error out on duplicated type/typeof/element + keywords + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 6c04e5ceb95068bb459b07307ecc3629d97a2043 + +commit 6c04e5ceb95068bb459b07307ecc3629d97a2043 +Author: Florian Westphal +Date: Tue Dec 19 16:22:32 2023 +0100 + + parser_bison: error out on duplicated type/typeof/element keywords + + Otherwise nft will leak the previous definition (expressions). + Also remove the nonsensical + + datatype_set($1->key, $3->dtype); + + This is a no-op, at this point: $1->key and $3 are identical. + + Signed-off-by: Florian Westphal + +Signed-off-by: Phil Sutter +--- + src/parser_bison.y | 38 ++++++++++++++++++++++++++++++++++++-- + 1 file changed, 36 insertions(+), 2 deletions(-) + +diff --git a/src/parser_bison.y b/src/parser_bison.y +index fa59207..0ab9dec 100644 +--- a/src/parser_bison.y ++++ b/src/parser_bison.y +@@ -2159,11 +2159,20 @@ set_block : /* empty */ { $$ = $-1; } + | set_block stmt_separator + | set_block TYPE data_type_expr stmt_separator close_scope_type + { ++ if (already_set($1->key, &@2, state)) { ++ expr_free($3); ++ YYERROR; ++ } ++ + $1->key = $3; + $$ = $1; + } + | set_block TYPEOF typeof_expr stmt_separator + { ++ if (already_set($1->key, &@2, state)) { ++ expr_free($3); ++ YYERROR; ++ } + $1->key = $3; + datatype_set($1->key, $3->dtype); + $$ = $1; +@@ -2191,6 +2200,10 @@ set_block : /* empty */ { $$ = $-1; } + } + | set_block ELEMENTS '=' set_block_expr + { ++ if (already_set($1->init, &@2, state)) { ++ expr_free($4); ++ YYERROR; ++ } + $1->init = $4; + $$ = $1; + } +@@ -2262,6 +2275,12 @@ map_block : /* empty */ { $$ = $-1; } + data_type_expr COLON map_block_data_interval data_type_expr + stmt_separator close_scope_type + { ++ if (already_set($1->key, &@2, state)) { ++ expr_free($3); ++ expr_free($6); ++ YYERROR; ++ } ++ + $1->key = $3; + $1->data = $6; + $1->data->flags |= $5; +@@ -2273,8 +2292,13 @@ map_block : /* empty */ { $$ = $-1; } + typeof_expr COLON typeof_data_expr + stmt_separator + { ++ if (already_set($1->key, &@2, state)) { ++ expr_free($3); ++ expr_free($5); ++ YYERROR; ++ } ++ + $1->key = $3; +- datatype_set($1->key, $3->dtype); + $1->data = $5; + + $1->flags |= NFT_SET_MAP; +@@ -2284,8 +2308,13 @@ map_block : /* empty */ { $$ = $-1; } + typeof_expr COLON INTERVAL typeof_expr + stmt_separator + { ++ if (already_set($1->key, &@2, state)) { ++ expr_free($3); ++ expr_free($6); ++ YYERROR; ++ } ++ + $1->key = $3; +- datatype_set($1->key, $3->dtype); + $1->data = $6; + $1->data->flags |= EXPR_F_INTERVAL; + +@@ -2296,6 +2325,11 @@ map_block : /* empty */ { $$ = $-1; } + data_type_expr COLON map_block_obj_type + stmt_separator close_scope_type + { ++ if (already_set($1->key, &@2, state)) { ++ expr_free($3); ++ YYERROR; ++ } ++ + $1->key = $3; + $1->objtype = $5; + $1->flags |= NFT_SET_OBJECT; diff --git a/0057-netlink-fix-stack-overflow-due-to-erroneous-rounding.patch b/0057-netlink-fix-stack-overflow-due-to-erroneous-rounding.patch new file mode 100644 index 0000000..8cef293 --- /dev/null +++ b/0057-netlink-fix-stack-overflow-due-to-erroneous-rounding.patch @@ -0,0 +1,81 @@ +From 0ef6256eae2581795a6aa9070876ae4d909c7f50 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:41 +0200 +Subject: [PATCH] netlink: fix stack overflow due to erroneous rounding + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit b9e19cc396347df8c7f8cf5d14ba1d6172040f16 + +commit b9e19cc396347df8c7f8cf5d14ba1d6172040f16 +Author: Florian Westphal +Date: Wed Dec 20 15:40:54 2023 +0100 + + netlink: fix stack overflow due to erroneous rounding + + Byteorder switch in this function may undersize the conversion + buffer by one byte, this needs to use div_round_up(). + + Signed-off-by: Florian Westphal + +Signed-off-by: Phil Sutter +--- + src/netlink.c | 11 ++++++++--- + .../bogons/nft-f/byteorder_switch_stack_overflow | 6 ++++++ + 2 files changed, 14 insertions(+), 3 deletions(-) + create mode 100644 tests/shell/testcases/bogons/nft-f/byteorder_switch_stack_overflow + +diff --git a/src/netlink.c b/src/netlink.c +index a366758..8637186 100644 +--- a/src/netlink.c ++++ b/src/netlink.c +@@ -254,6 +254,11 @@ static int netlink_export_pad(unsigned char *data, const mpz_t v, + return netlink_padded_len(i->len) / BITS_PER_BYTE; + } + ++static void byteorder_switch_expr_value(mpz_t v, const struct expr *e) ++{ ++ mpz_switch_byteorder(v, div_round_up(e->len, BITS_PER_BYTE)); ++} ++ + static int __netlink_gen_concat_key(uint32_t flags, const struct expr *i, + unsigned char *data) + { +@@ -268,7 +273,7 @@ static int __netlink_gen_concat_key(uint32_t flags, const struct expr *i, + + if (expr_basetype(expr)->type == TYPE_INTEGER && + expr->byteorder == BYTEORDER_HOST_ENDIAN) +- mpz_switch_byteorder(expr->value, expr->len / BITS_PER_BYTE); ++ byteorder_switch_expr_value(expr->value, expr); + + i = expr; + break; +@@ -280,7 +285,7 @@ static int __netlink_gen_concat_key(uint32_t flags, const struct expr *i, + mpz_init_bitmask(v, i->len - i->prefix_len); + + if (i->byteorder == BYTEORDER_HOST_ENDIAN) +- mpz_switch_byteorder(v, i->len / BITS_PER_BYTE); ++ byteorder_switch_expr_value(v, i); + + mpz_add(v, i->prefix->value, v); + count = netlink_export_pad(data, v, i); +@@ -298,7 +303,7 @@ static int __netlink_gen_concat_key(uint32_t flags, const struct expr *i, + expr = (struct expr *)i; + if (expr_basetype(expr)->type == TYPE_INTEGER && + expr->byteorder == BYTEORDER_HOST_ENDIAN) +- mpz_switch_byteorder(expr->value, expr->len / BITS_PER_BYTE); ++ byteorder_switch_expr_value(expr->value, expr); + break; + default: + BUG("invalid expression type '%s' in set", expr_ops(i)->name); +diff --git a/tests/shell/testcases/bogons/nft-f/byteorder_switch_stack_overflow b/tests/shell/testcases/bogons/nft-f/byteorder_switch_stack_overflow +new file mode 100644 +index 0000000..0164052 +--- /dev/null ++++ b/tests/shell/testcases/bogons/nft-f/byteorder_switch_stack_overflow +@@ -0,0 +1,6 @@ ++table inet x { ++ chain nat_dns_acme { ++ udp length . @th,260,118 vmap { 47-63 . 0xe373135363130333131303735353203 : goto nat_dns_dnstc, } ++ drop ++ } ++} diff --git a/0058-parser_bison-ensure-all-timeout-policy-names-are-rel.patch b/0058-parser_bison-ensure-all-timeout-policy-names-are-rel.patch new file mode 100644 index 0000000..659021c --- /dev/null +++ b/0058-parser_bison-ensure-all-timeout-policy-names-are-rel.patch @@ -0,0 +1,125 @@ +From 023ceb40acf7fd6dad65c149243b2dbc16838f87 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:41 +0200 +Subject: [PATCH] parser_bison: ensure all timeout policy names are released + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 86a496928420046e9d32317f09db050e8351b10e + +commit 86a496928420046e9d32317f09db050e8351b10e +Author: Florian Westphal +Date: Tue Dec 12 13:32:24 2023 +0100 + + parser_bison: ensure all timeout policy names are released + + We need to add a custom destructor for this structure, it + contains the dynamically allocated names. + + a:5:55-55: Error: syntax error, unexpected '}', expecting string + policy = { estabQisheestablished : 2m3s, cd : 2m3s, } + + ==562373==ERROR: LeakSanitizer: detected memory leaks + + Indirect leak of 160 byte(s) in 2 object(s) allocated from: + #1 0x5a565b in xmalloc src/utils.c:31:8 + #2 0x5a565b in xzalloc src/utils.c:70:8 + #3 0x3d9352 in nft_parse_bison_filename src/libnftables.c:520:8 + [..] + + Fixes: c7c94802679c ("src: add ct timeout support") + Signed-off-by: Florian Westphal + +Signed-off-by: Phil Sutter +--- + src/parser_bison.y | 32 ++++++++++++++++--- + .../testcases/bogons/nft-f/ct_timeout_memleak | 7 ++++ + 2 files changed, 34 insertions(+), 5 deletions(-) + create mode 100644 tests/shell/testcases/bogons/nft-f/ct_timeout_memleak + +diff --git a/src/parser_bison.y b/src/parser_bison.y +index 0ab9dec..e81336f 100644 +--- a/src/parser_bison.y ++++ b/src/parser_bison.y +@@ -173,6 +173,24 @@ static struct expr *ifname_expr_alloc(const struct location *location, + return expr; + } + ++static void timeout_state_free(struct timeout_state *s) ++{ ++ free_const(s->timeout_str); ++ free(s); ++} ++ ++static void timeout_states_free(struct list_head *list) ++{ ++ struct timeout_state *ts, *next; ++ ++ list_for_each_entry_safe(ts, next, list, head) { ++ list_del(&ts->head); ++ timeout_state_free(ts); ++ } ++ ++ free(list); ++} ++ + #define YYLLOC_DEFAULT(Current, Rhs, N) location_update(&Current, Rhs, N) + + #define symbol_value(loc, str) \ +@@ -230,6 +248,7 @@ int nft_lex(void *, void *, void *); + uint16_t kind; /* must allow > 255 for SACK1, 2.. hack */ + uint8_t field; + } tcp_kind_field; ++ struct timeout_state *timeout_state; + } + + %token TOKEN_EOF 0 "end of file" +@@ -969,8 +988,11 @@ int nft_lex(void *, void *, void *); + + %type ct_l4protoname ct_obj_type ct_cmd_type + +-%type timeout_states timeout_state +-%destructor { free($$); } timeout_states timeout_state ++%type timeout_state ++%destructor { timeout_state_free($$); } timeout_state ++ ++%type timeout_states ++%destructor { timeout_states_free($$); } timeout_states + + %type xfrm_state_key xfrm_state_proto_key xfrm_dir xfrm_spnum + %type xfrm_expr +@@ -4896,11 +4918,11 @@ timeout_states : timeout_state + { + $$ = xmalloc(sizeof(*$$)); + init_list_head($$); +- list_add_tail($1, $$); ++ list_add_tail(&$1->head, $$); + } + | timeout_states COMMA timeout_state + { +- list_add_tail($3, $1); ++ list_add_tail(&$3->head, $1); + $$ = $1; + } + ; +@@ -4914,7 +4936,7 @@ timeout_state : STRING COLON time_spec_or_num_s + ts->timeout_value = $3; + ts->location = @1; + init_list_head(&ts->head); +- $$ = &ts->head; ++ $$ = ts; + } + ; + +diff --git a/tests/shell/testcases/bogons/nft-f/ct_timeout_memleak b/tests/shell/testcases/bogons/nft-f/ct_timeout_memleak +new file mode 100644 +index 0000000..014525a +--- /dev/null ++++ b/tests/shell/testcases/bogons/nft-f/ct_timeout_memleak +@@ -0,0 +1,7 @@ ++table ip filter { ++ ct timeout cttime { ++ protocol tcp ++ l3proto ip ++ policy = { estabQisheestablished : 2m3s, cd : 2m3s, } ++ } ++} diff --git a/0059-tests-shell-prefer-project-nft-to-system-wide-nft.patch b/0059-tests-shell-prefer-project-nft-to-system-wide-nft.patch new file mode 100644 index 0000000..96d138c --- /dev/null +++ b/0059-tests-shell-prefer-project-nft-to-system-wide-nft.patch @@ -0,0 +1,94 @@ +From 21c9b6d963d6aeff380324e411a0a871b422b923 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:41 +0200 +Subject: [PATCH] tests: shell: prefer project nft to system-wide nft + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 8440983b7671feb3bb86c4c148af743addf3e00b + +commit 8440983b7671feb3bb86c4c148af743addf3e00b +Author: Florian Westphal +Date: Sat Jan 6 13:23:24 2024 +0100 + + tests: shell: prefer project nft to system-wide nft + + Use $NFT (src/nft, in-tree binary), not the one installed by the distro. + Else we may not find newly added bugs unless user did "make install" or + bug has propagated to release. + + Signed-off-by: Florian Westphal + +Signed-off-by: Phil Sutter +--- + tests/shell/testcases/packetpath/payload | 38 +++++++++---------- + tests/shell/testcases/parsing/large_rule_pipe | 2 +- + 2 files changed, 20 insertions(+), 20 deletions(-) + +diff --git a/tests/shell/testcases/packetpath/payload b/tests/shell/testcases/packetpath/payload +index 1a89d85..9f4587d 100755 +--- a/tests/shell/testcases/packetpath/payload ++++ b/tests/shell/testcases/packetpath/payload +@@ -102,19 +102,19 @@ table inet payload_inet { + + ip netns exec "$ns1" $NFT list ruleset + +- ip netns exec "$ns1" nft list counter netdev payload_netdev ingress | grep -v "packets 0" > /dev/null || exit 1 +- ip netns exec "$ns1" nft list counter netdev payload_netdev mangle_ingress | grep -v "packets 0" > /dev/null || exit 1 +- ip netns exec "$ns1" nft list counter netdev payload_netdev mangle_ingress_match | grep -v "packets 0" > /dev/null || exit 1 +- ip netns exec "$ns1" nft list counter netdev payload_netdev egress | grep -v "packets 0" > /dev/null || exit 1 +- ip netns exec "$ns1" nft list counter netdev payload_netdev mangle_egress | grep -v "packets 0" > /dev/null || exit 1 +- ip netns exec "$ns1" nft list counter netdev payload_netdev mangle_egress_match | grep -v "packets 0" > /dev/null || exit 1 +- +- ip netns exec "$ns1" nft list counter inet payload_inet input | grep -v "packets 0" > /dev/null || exit 1 +- ip netns exec "$ns1" nft list counter inet payload_inet mangle_input | grep -v "packets 0" > /dev/null || exit 1 +- ip netns exec "$ns1" nft list counter inet payload_inet mangle_input_match | grep -v "packets 0" > /dev/null || exit 1 +- ip netns exec "$ns1" nft list counter inet payload_inet output | grep -v "packets 0" > /dev/null || exit 1 +- ip netns exec "$ns1" nft list counter inet payload_inet mangle_output | grep -v "packets 0" > /dev/null || exit 1 +- ip netns exec "$ns1" nft list counter inet payload_inet mangle_output_match | grep -v "packets 0" > /dev/null || exit 1 ++ ip netns exec "$ns1" $NFT list counter netdev payload_netdev ingress | grep -v "packets 0" > /dev/null || exit 1 ++ ip netns exec "$ns1" $NFT list counter netdev payload_netdev mangle_ingress | grep -v "packets 0" > /dev/null || exit 1 ++ ip netns exec "$ns1" $NFT list counter netdev payload_netdev mangle_ingress_match | grep -v "packets 0" > /dev/null || exit 1 ++ ip netns exec "$ns1" $NFT list counter netdev payload_netdev egress | grep -v "packets 0" > /dev/null || exit 1 ++ ip netns exec "$ns1" $NFT list counter netdev payload_netdev mangle_egress | grep -v "packets 0" > /dev/null || exit 1 ++ ip netns exec "$ns1" $NFT list counter netdev payload_netdev mangle_egress_match | grep -v "packets 0" > /dev/null || exit 1 ++ ++ ip netns exec "$ns1" $NFT list counter inet payload_inet input | grep -v "packets 0" > /dev/null || exit 1 ++ ip netns exec "$ns1" $NFT list counter inet payload_inet mangle_input | grep -v "packets 0" > /dev/null || exit 1 ++ ip netns exec "$ns1" $NFT list counter inet payload_inet mangle_input_match | grep -v "packets 0" > /dev/null || exit 1 ++ ip netns exec "$ns1" $NFT list counter inet payload_inet output | grep -v "packets 0" > /dev/null || exit 1 ++ ip netns exec "$ns1" $NFT list counter inet payload_inet mangle_output | grep -v "packets 0" > /dev/null || exit 1 ++ ip netns exec "$ns1" $NFT list counter inet payload_inet mangle_output_match | grep -v "packets 0" > /dev/null || exit 1 + + # + # ... next stage +@@ -166,12 +166,12 @@ RULESET="table bridge payload_bridge { + + ip netns exec "$ns1" $NFT list ruleset + +- ip netns exec "$ns1" nft list counter bridge payload_bridge input | grep -v "packets 0" > /dev/null || exit 1 +- ip netns exec "$ns1" nft list counter bridge payload_bridge mangle_input | grep -v "packets 0" > /dev/null || exit 1 +- ip netns exec "$ns1" nft list counter bridge payload_bridge mangle_input_match | grep -v "packets 0" > /dev/null || exit 1 +- ip netns exec "$ns1" nft list counter bridge payload_bridge output | grep -v "packets 0" > /dev/null || exit 1 +- ip netns exec "$ns1" nft list counter bridge payload_bridge mangle_output | grep -v "packets 0" > /dev/null || exit 1 +- ip netns exec "$ns1" nft list counter bridge payload_bridge mangle_output_match | grep -v "packets 0" > /dev/null || exit 1 ++ ip netns exec "$ns1" $NFT list counter bridge payload_bridge input | grep -v "packets 0" > /dev/null || exit 1 ++ ip netns exec "$ns1" $NFT list counter bridge payload_bridge mangle_input | grep -v "packets 0" > /dev/null || exit 1 ++ ip netns exec "$ns1" $NFT list counter bridge payload_bridge mangle_input_match | grep -v "packets 0" > /dev/null || exit 1 ++ ip netns exec "$ns1" $NFT list counter bridge payload_bridge output | grep -v "packets 0" > /dev/null || exit 1 ++ ip netns exec "$ns1" $NFT list counter bridge payload_bridge mangle_output | grep -v "packets 0" > /dev/null || exit 1 ++ ip netns exec "$ns1" $NFT list counter bridge payload_bridge mangle_output_match | grep -v "packets 0" > /dev/null || exit 1 + } + + run_test "4" "10.141.10.2" "10.141.10.3" "24" +diff --git a/tests/shell/testcases/parsing/large_rule_pipe b/tests/shell/testcases/parsing/large_rule_pipe +index fac0afa..b6760c0 100755 +--- a/tests/shell/testcases/parsing/large_rule_pipe ++++ b/tests/shell/testcases/parsing/large_rule_pipe +@@ -566,6 +566,6 @@ table inet firewalld { + } + }" + +-( echo "flush ruleset;"; echo "${RULESET}" ) | nft -f - ++( echo "flush ruleset;"; echo "${RULESET}" ) | $NFT -f - + + exit 0 diff --git a/0060-doc-incorrect-datatype-description-for-icmpv6_type-a.patch b/0060-doc-incorrect-datatype-description-for-icmpv6_type-a.patch new file mode 100644 index 0000000..ec69f21 --- /dev/null +++ b/0060-doc-incorrect-datatype-description-for-icmpv6_type-a.patch @@ -0,0 +1,49 @@ +From 5104ecc5b5f0726ab58df27742c9c3cf8a65f1b3 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:41 +0200 +Subject: [PATCH] doc: incorrect datatype description for icmpv6_type and + icmpvx_code + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 9caa5142f95557decc19925ad37411f1df4589a2 + +commit 9caa5142f95557decc19925ad37411f1df4589a2 +Author: Pablo Neira Ayuso +Date: Tue Jan 9 12:42:20 2024 +0100 + + doc: incorrect datatype description for icmpv6_type and icmpvx_code + + Fix incorrect description in manpage: + + ICMPV6 TYPE TYPE is icmpv6_type + ICMPVX CODE TYPE is icmpx_code + + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + doc/data-types.txt | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/doc/data-types.txt b/doc/data-types.txt +index 961fc62..e5ee91a 100644 +--- a/doc/data-types.txt ++++ b/doc/data-types.txt +@@ -270,7 +270,7 @@ ICMPV6 TYPE TYPE + |================== + |Name | Keyword | Size | Base type + |ICMPv6 Type | +-icmpx_code | ++icmpv6_type | + 8 bit | + integer + |=================== +@@ -364,7 +364,7 @@ ICMPVX CODE TYPE + |================== + |Name | Keyword | Size | Base type + |ICMPvX Code | +-icmpv6_type | ++icmpx_code | + 8 bit | + integer + |=================== diff --git a/0061-evaluate-add-missing-range-checks-for-dup-fwd-and-pa.patch b/0061-evaluate-add-missing-range-checks-for-dup-fwd-and-pa.patch new file mode 100644 index 0000000..4436aec --- /dev/null +++ b/0061-evaluate-add-missing-range-checks-for-dup-fwd-and-pa.patch @@ -0,0 +1,228 @@ +From aaaf1ed4ab6ecb027ea58f49812c05fed9651cba Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:41 +0200 +Subject: [PATCH] evaluate: add missing range checks for dup,fwd and payload + statements + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 4121175cc243a15bdb8c226a335f67cedd98680e + +commit 4121175cc243a15bdb8c226a335f67cedd98680e +Author: Florian Westphal +Date: Thu Jan 11 18:14:16 2024 +0100 + + evaluate: add missing range checks for dup,fwd and payload statements + + Else we assert with: + BUG: unknown expression type range + nft: src/netlink_linearize.c:912: netlink_gen_expr: Assertion `0' failed. + + While at it, condense meta and exthdr to reuse the same helper. + + Signed-off-by: Florian Westphal + +Signed-off-by: Phil Sutter +--- + src/evaluate.c | 88 +++++++++++-------- + .../testcases/bogons/nft-f/dup_fwd_ranges | 14 +++ + .../nft-f/unknown_expr_type_range_assert | 8 +- + 3 files changed, 69 insertions(+), 41 deletions(-) + create mode 100644 tests/shell/testcases/bogons/nft-f/dup_fwd_ranges + +diff --git a/src/evaluate.c b/src/evaluate.c +index c655e3a..06ad554 100644 +--- a/src/evaluate.c ++++ b/src/evaluate.c +@@ -74,6 +74,33 @@ static int __fmtstring(3, 4) set_error(struct eval_ctx *ctx, + return -1; + } + ++static const char *stmt_name(const struct stmt *stmt) ++{ ++ switch (stmt->ops->type) { ++ case STMT_NAT: ++ switch (stmt->nat.type) { ++ case NFT_NAT_SNAT: ++ return "snat"; ++ case NFT_NAT_DNAT: ++ return "dnat"; ++ case NFT_NAT_REDIR: ++ return "redirect"; ++ case NFT_NAT_MASQ: ++ return "masquerade"; ++ } ++ break; ++ default: ++ break; ++ } ++ ++ return stmt->ops->name; ++} ++ ++static int stmt_error_range(struct eval_ctx *ctx, const struct stmt *stmt, const struct expr *e) ++{ ++ return expr_error(ctx->msgs, e, "%s: range argument not supported", stmt_name(stmt)); ++} ++ + static void key_fix_dtype_byteorder(struct expr *key) + { + const struct datatype *dtype = key->dtype; +@@ -2980,13 +3007,8 @@ static int stmt_evaluate_exthdr(struct eval_ctx *ctx, struct stmt *stmt) + if (ret < 0) + return ret; + +- switch (stmt->exthdr.val->etype) { +- case EXPR_RANGE: +- return expr_error(ctx->msgs, stmt->exthdr.val, +- "cannot be a range"); +- default: +- break; +- } ++ if (stmt->exthdr.val->etype == EXPR_RANGE) ++ return stmt_error_range(ctx, stmt, stmt->exthdr.val); + + return 0; + } +@@ -3019,6 +3041,9 @@ static int stmt_evaluate_payload(struct eval_ctx *ctx, struct stmt *stmt) + payload->byteorder) < 0) + return -1; + ++ if (stmt->payload.val->etype == EXPR_RANGE) ++ return stmt_error_range(ctx, stmt, stmt->payload.val); ++ + need_csum = stmt_evaluate_payload_need_csum(payload); + + if (!payload_needs_adjustment(payload)) { +@@ -3178,15 +3203,8 @@ static int stmt_evaluate_meta(struct eval_ctx *ctx, struct stmt *stmt) + if (ret < 0) + return ret; + +- switch (stmt->meta.expr->etype) { +- case EXPR_RANGE: +- ret = expr_error(ctx->msgs, stmt->meta.expr, +- "Meta expression cannot be a range"); +- break; +- default: +- break; +- +- } ++ if (stmt->meta.expr->etype == EXPR_RANGE) ++ return stmt_error_range(ctx, stmt, stmt->meta.expr); + + return ret; + } +@@ -3209,6 +3227,9 @@ static int stmt_evaluate_ct(struct eval_ctx *ctx, struct stmt *stmt) + return stmt_error(ctx, stmt, + "ct secmark must not be set to constant value"); + ++ if (stmt->ct.expr->etype == EXPR_RANGE) ++ return stmt_error_range(ctx, stmt, stmt->ct.expr); ++ + return 0; + } + +@@ -3719,28 +3740,6 @@ static int nat_evaluate_transport(struct eval_ctx *ctx, struct stmt *stmt, + return 0; + } + +-static const char *stmt_name(const struct stmt *stmt) +-{ +- switch (stmt->ops->type) { +- case STMT_NAT: +- switch (stmt->nat.type) { +- case NFT_NAT_SNAT: +- return "snat"; +- case NFT_NAT_DNAT: +- return "dnat"; +- case NFT_NAT_REDIR: +- return "redirect"; +- case NFT_NAT_MASQ: +- return "masquerade"; +- } +- break; +- default: +- break; +- } +- +- return stmt->ops->name; +-} +- + static int stmt_evaluate_l3proto(struct eval_ctx *ctx, + struct stmt *stmt, uint8_t family) + { +@@ -4132,6 +4131,9 @@ static int stmt_evaluate_dup(struct eval_ctx *ctx, struct stmt *stmt) + &stmt->dup.dev); + if (err < 0) + return err; ++ ++ if (stmt->dup.dev->etype == EXPR_RANGE) ++ return stmt_error_range(ctx, stmt, stmt->dup.dev); + } + break; + case NFPROTO_NETDEV: +@@ -4150,6 +4152,10 @@ static int stmt_evaluate_dup(struct eval_ctx *ctx, struct stmt *stmt) + default: + return stmt_error(ctx, stmt, "unsupported family"); + } ++ ++ if (stmt->dup.to->etype == EXPR_RANGE) ++ return stmt_error_range(ctx, stmt, stmt->dup.to); ++ + return 0; + } + +@@ -4171,6 +4177,9 @@ static int stmt_evaluate_fwd(struct eval_ctx *ctx, struct stmt *stmt) + if (err < 0) + return err; + ++ if (stmt->fwd.dev->etype == EXPR_RANGE) ++ return stmt_error_range(ctx, stmt, stmt->fwd.dev); ++ + if (stmt->fwd.addr != NULL) { + switch (stmt->fwd.family) { + case NFPROTO_IPV4: +@@ -4189,6 +4198,9 @@ static int stmt_evaluate_fwd(struct eval_ctx *ctx, struct stmt *stmt) + &stmt->fwd.addr); + if (err < 0) + return err; ++ ++ if (stmt->fwd.addr->etype == EXPR_RANGE) ++ return stmt_error_range(ctx, stmt, stmt->fwd.addr); + } + break; + default: +diff --git a/tests/shell/testcases/bogons/nft-f/dup_fwd_ranges b/tests/shell/testcases/bogons/nft-f/dup_fwd_ranges +new file mode 100644 +index 0000000..efaff9e +--- /dev/null ++++ b/tests/shell/testcases/bogons/nft-f/dup_fwd_ranges +@@ -0,0 +1,14 @@ ++define dev = "1"-"2" ++ ++table netdev t { ++ chain c { ++ fwd to 1-2 ++ dup to 1-2 ++ } ++} ++ ++table ip t { ++ chain c { ++ dup to 1-2 device $dev ++ } ++} +diff --git a/tests/shell/testcases/bogons/nft-f/unknown_expr_type_range_assert b/tests/shell/testcases/bogons/nft-f/unknown_expr_type_range_assert +index 234dd62..e620673 100644 +--- a/tests/shell/testcases/bogons/nft-f/unknown_expr_type_range_assert ++++ b/tests/shell/testcases/bogons/nft-f/unknown_expr_type_range_assert +@@ -1,5 +1,7 @@ + table ip x { +- chain k { +- meta mark set 0x001-3434 +- } ++ chain k { ++ meta mark set 0x001-3434 ++ ct mark set 0x001-3434 ++ tcp dport set 1-3 ++ } + } diff --git a/0062-evaluate-skip-anonymous-set-optimization-for-concate.patch b/0062-evaluate-skip-anonymous-set-optimization-for-concate.patch new file mode 100644 index 0000000..beb41aa --- /dev/null +++ b/0062-evaluate-skip-anonymous-set-optimization-for-concate.patch @@ -0,0 +1,57 @@ +From 475315fb01bac6b53833c727add0cf1c427a75b7 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:42 +0200 +Subject: [PATCH] evaluate: skip anonymous set optimization for concatenations + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 6bc6673fc88c8a3e3dd5504b2d24a6d6bc2f8427 + +commit 6bc6673fc88c8a3e3dd5504b2d24a6d6bc2f8427 +Author: Pablo Neira Ayuso +Date: Wed Jan 10 18:18:50 2024 +0100 + + evaluate: skip anonymous set optimization for concatenations + + Concatenation is only supported with sets. Moreover, stripping of the + set leads to broken ruleset listing, therefore, skip this optimization + for the concatenations. + + Fixes: fa17b17ea74a ("evaluate: revisit anonymous set with single element optimization") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/evaluate.c | 20 +++++++++++--------- + 1 file changed, 11 insertions(+), 9 deletions(-) + +diff --git a/src/evaluate.c b/src/evaluate.c +index 06ad554..8f98b48 100644 +--- a/src/evaluate.c ++++ b/src/evaluate.c +@@ -2507,15 +2507,17 @@ static int expr_evaluate_relational(struct eval_ctx *ctx, struct expr **expr) + return expr_binary_error(ctx->msgs, right, left, + "Cannot be used with right hand side constant value"); + +- switch (rel->op) { +- case OP_EQ: +- case OP_IMPLICIT: +- case OP_NEQ: +- if (right->etype == EXPR_SET && right->size == 1) +- optimize_singleton_set(rel, &right); +- break; +- default: +- break; ++ if (left->etype != EXPR_CONCAT) { ++ switch (rel->op) { ++ case OP_EQ: ++ case OP_IMPLICIT: ++ case OP_NEQ: ++ if (right->etype == EXPR_SET && right->size == 1) ++ optimize_singleton_set(rel, &right); ++ break; ++ default: ++ break; ++ } + } + + switch (rel->op) { diff --git a/0063-evaluate-do-not-fetch-next-expression-on-runaway-num.patch b/0063-evaluate-do-not-fetch-next-expression-on-runaway-num.patch new file mode 100644 index 0000000..c4634a5 --- /dev/null +++ b/0063-evaluate-do-not-fetch-next-expression-on-runaway-num.patch @@ -0,0 +1,45 @@ +From bd77070f6c17a0dba479ed5a45b940d8605a91f3 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:42 +0200 +Subject: [PATCH] evaluate: do not fetch next expression on runaway number of + concatenation components + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 955bb6d31c90453e43043346c917646ddc4e5c4e + +commit 955bb6d31c90453e43043346c917646ddc4e5c4e +Author: Pablo Neira Ayuso +Date: Wed Jan 10 18:20:47 2024 +0100 + + evaluate: do not fetch next expression on runaway number of concatenation components + + If this is the last expression, then the runaway flag is set on and + evaluation bails in the next iteration, do not fetch next list element + which refers to the list head. + + I found this by code inspection, I could not trigger any crash with this + one. + + Fixes: ae1d54d1343f ("evaluate: do not crash on runaway number of concatenation components") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/evaluate.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/src/evaluate.c b/src/evaluate.c +index 8f98b48..079f340 100644 +--- a/src/evaluate.c ++++ b/src/evaluate.c +@@ -1591,8 +1591,8 @@ static int expr_evaluate_concat(struct eval_ctx *ctx, struct expr **expr) + if (key && expressions) { + if (list_is_last(&key->list, expressions)) + runaway = true; +- +- key = list_next_entry(key, list); ++ else ++ key = list_next_entry(key, list); + } + + ctx->inner_desc = NULL; diff --git a/0064-evaluate-error-out-when-store-needs-more-than-one-12.patch b/0064-evaluate-error-out-when-store-needs-more-than-one-12.patch new file mode 100644 index 0000000..8e89bcc --- /dev/null +++ b/0064-evaluate-error-out-when-store-needs-more-than-one-12.patch @@ -0,0 +1,62 @@ +From 9f7891ddd76afb0472cad8cec8e7cfed12f55d99 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:42 +0200 +Subject: [PATCH] evaluate: error out when store needs more than one 128bit + register of align fixup + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 8a66de2a15943b2fbf960967cdbcbd0a148cb114 + +commit 8a66de2a15943b2fbf960967cdbcbd0a148cb114 +Author: Florian Westphal +Date: Mon Jan 15 14:11:17 2024 +0100 + + evaluate: error out when store needs more than one 128bit register of align fixup + + Else this gives: + nft: evaluate.c:2983: stmt_evaluate_payload: Assertion `sizeof(data) * BITS_PER_BYTE >= masklen' failed. + + For loads, this is already prevented via expr_evaluate_bits() which has: + + if (masklen > NFT_REG_SIZE * BITS_PER_BYTE) + return expr_error(ctx->msgs, expr, "mask length %u exceeds allowed maximum of %u\n", + masklen, NFT_REG_SIZE * BITS_PER_BYTE); + + But for the store path this isn't called. + The reproducer asks to store a 128 bit integer at bit offset 1, i.e. + 17 bytes would need to be munged, but we can only handle up to 16 bytes + (one pseudo-register). + + Fixes: 78936d50f306 ("evaluate: add support to set IPv6 non-byte header fields") + Signed-off-by: Florian Westphal + +Signed-off-by: Phil Sutter +--- + src/evaluate.c | 5 +++++ + .../testcases/bogons/nft-f/payload_expr_unaligned_store | 1 + + 2 files changed, 6 insertions(+) + create mode 100644 tests/shell/testcases/bogons/nft-f/payload_expr_unaligned_store + +diff --git a/src/evaluate.c b/src/evaluate.c +index 079f340..541299e 100644 +--- a/src/evaluate.c ++++ b/src/evaluate.c +@@ -3065,6 +3065,11 @@ static int stmt_evaluate_payload(struct eval_ctx *ctx, struct stmt *stmt) + payload_byte_size = div_round_up(payload->len + extra_len, + BITS_PER_BYTE); + ++ if (payload_byte_size > sizeof(data)) ++ return expr_error(ctx->msgs, stmt->payload.expr, ++ "uneven load cannot span more than %u bytes, got %u", ++ sizeof(data), payload_byte_size); ++ + if (need_csum && payload_byte_size & 1) { + payload_byte_size++; + +diff --git a/tests/shell/testcases/bogons/nft-f/payload_expr_unaligned_store b/tests/shell/testcases/bogons/nft-f/payload_expr_unaligned_store +new file mode 100644 +index 0000000..c1358df +--- /dev/null ++++ b/tests/shell/testcases/bogons/nft-f/payload_expr_unaligned_store +@@ -0,0 +1 @@ ++add rule f i @th,1,128 set 1 diff --git a/0065-rule-fix-sym-refcount-assertion.patch b/0065-rule-fix-sym-refcount-assertion.patch new file mode 100644 index 0000000..67a07ee --- /dev/null +++ b/0065-rule-fix-sym-refcount-assertion.patch @@ -0,0 +1,64 @@ +From a7072eb98d9549db87c4d76eb6ca3556728277b5 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:42 +0200 +Subject: [PATCH] rule: fix sym refcount assertion + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit b73298405cda74b3a87a1818bb92f53298d34170 + +commit b73298405cda74b3a87a1818bb92f53298d34170 +Author: Florian Westphal +Date: Mon Jan 15 14:27:15 2024 +0100 + + rule: fix sym refcount assertion + + Scope release must happen last. + afl provided a reproducer where policy is a define, because + scope is released too early we get: + nft: src/rule.c:559: scope_release: Assertion `sym->refcnt == 1' failed. + + ... because chain->policy is EXPR_SYMBOL. + + Fixes: 627c451b2351 ("src: allow variables in the chain priority specification") + Signed-off-by: Florian Westphal + +Signed-off-by: Phil Sutter +--- + src/rule.c | 6 +++++- + tests/shell/testcases/bogons/nft-f/define_policy_assert | 3 +++ + 2 files changed, 8 insertions(+), 1 deletion(-) + create mode 100644 tests/shell/testcases/bogons/nft-f/define_policy_assert + +diff --git a/src/rule.c b/src/rule.c +index 633fae7..e2d972c 100644 +--- a/src/rule.c ++++ b/src/rule.c +@@ -729,7 +729,6 @@ void chain_free(struct chain *chain) + list_for_each_entry_safe(rule, next, &chain->rules, list) + rule_free(rule); + handle_free(&chain->handle); +- scope_release(&chain->scope); + free_const(chain->type.str); + expr_free(chain->dev_expr); + for (i = 0; i < chain->dev_array_len; i++) +@@ -738,6 +737,11 @@ void chain_free(struct chain *chain) + expr_free(chain->priority.expr); + expr_free(chain->policy); + free_const(chain->comment); ++ ++ /* MUST be released after all expressions, they could ++ * hold refcounts. ++ */ ++ scope_release(&chain->scope); + free(chain); + } + +diff --git a/tests/shell/testcases/bogons/nft-f/define_policy_assert b/tests/shell/testcases/bogons/nft-f/define_policy_assert +new file mode 100644 +index 0000000..f1e58b5 +--- /dev/null ++++ b/tests/shell/testcases/bogons/nft-f/define_policy_assert +@@ -0,0 +1,3 @@ ++chain y x { priority filter ++define p = foo ++policy $p diff --git a/0066-json-Support-sets-auto-merge-option.patch b/0066-json-Support-sets-auto-merge-option.patch new file mode 100644 index 0000000..af2259d --- /dev/null +++ b/0066-json-Support-sets-auto-merge-option.patch @@ -0,0 +1,85 @@ +From 6242822b5f19721538fb9296a048ea84acee5092 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:42 +0200 +Subject: [PATCH] json: Support sets' auto-merge option + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit a4034c66b03e4d526fbad78da9cd38da9f1f43a5 + +commit a4034c66b03e4d526fbad78da9cd38da9f1f43a5 +Author: Phil Sutter +Date: Wed Jan 31 17:30:24 2024 +0100 + + json: Support sets' auto-merge option + + If enabled, list the option as additional attribute with boolean value. + + Fixes: e70354f53e9f6 ("libnftables: Implement JSON output support") + Closes: https://bugzilla.netfilter.org/show_bug.cgi?id=1734 + Signed-off-by: Phil Sutter + +Signed-off-by: Phil Sutter +--- + doc/libnftables-json.adoc | 8 ++++++-- + src/json.c | 2 ++ + src/parser_json.c | 1 + + 3 files changed, 9 insertions(+), 2 deletions(-) + +diff --git a/doc/libnftables-json.adoc b/doc/libnftables-json.adoc +index c5e5f23..3bbb6cd 100644 +--- a/doc/libnftables-json.adoc ++++ b/doc/libnftables-json.adoc +@@ -321,7 +321,8 @@ ____ + "elem":* 'SET_ELEMENTS'*, + "timeout":* 'NUMBER'*, + "gc-interval":* 'NUMBER'*, +- "size":* 'NUMBER' ++ "size":* 'NUMBER'*, ++ "auto-merge":* 'BOOLEAN' + *}}* + + *{ "map": { +@@ -336,7 +337,8 @@ ____ + "elem":* 'SET_ELEMENTS'*, + "timeout":* 'NUMBER'*, + "gc-interval":* 'NUMBER'*, +- "size":* 'NUMBER' ++ "size":* 'NUMBER'*, ++ "auto-merge":* 'BOOLEAN' + *}}* + + 'SET_TYPE' := 'STRING' | *[* 'SET_TYPE_LIST' *]* +@@ -375,6 +377,8 @@ that they translate a unique key to a value. + Garbage collector interval in seconds. + *size*:: + Maximum number of elements supported. ++*auto-merge*:: ++ Automatic merging of adjacent/overlapping set elements in interval sets. + + ==== TYPE + The set type might be a string, such as *"ipv4_addr"* or an array +diff --git a/src/json.c b/src/json.c +index 6809cd5..b3e1e4e 100644 +--- a/src/json.c ++++ b/src/json.c +@@ -194,6 +194,8 @@ static json_t *set_print_json(struct output_ctx *octx, const struct set *set) + tmp = json_pack("i", set->gc_int / 1000); + json_object_set_new(root, "gc-interval", tmp); + } ++ if (set->automerge) ++ json_object_set_new(root, "auto-merge", json_true()); + + if (!nft_output_terse(octx) && set->init && set->init->size > 0) { + json_t *array = json_array(); +diff --git a/src/parser_json.c b/src/parser_json.c +index 2acc248..9537c9d 100644 +--- a/src/parser_json.c ++++ b/src/parser_json.c +@@ -3407,6 +3407,7 @@ static struct cmd *json_parse_cmd_add_set(struct json_ctx *ctx, json_t *root, + if (!json_unpack(root, "{s:i}", "gc-interval", &set->gc_int)) + set->gc_int *= 1000; + json_unpack(root, "{s:i}", "size", &set->desc.size); ++ json_unpack(root, "{s:b}", "auto-merge", &set->automerge); + + if (!json_unpack(root, "{s:o}", "stmt", &stmt_json)) + json_parse_set_stmt_list(ctx, &set->stmt_list, stmt_json); diff --git a/0067-evaluate-don-t-assert-on-net-transport-header-confli.patch b/0067-evaluate-don-t-assert-on-net-transport-header-confli.patch new file mode 100644 index 0000000..01f949e --- /dev/null +++ b/0067-evaluate-don-t-assert-on-net-transport-header-confli.patch @@ -0,0 +1,181 @@ +From 9a0ab03e46bcfadf423d284bb90e11c8e6f04fe8 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:42 +0200 +Subject: [PATCH] evaluate: don't assert on net/transport header conflict + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 3a734d60813193974a4a0e8ed0af3349f8857ec9 + +commit 3a734d60813193974a4a0e8ed0af3349f8857ec9 +Author: Florian Westphal +Date: Tue Jan 16 15:21:00 2024 +0100 + + evaluate: don't assert on net/transport header conflict + + before: + nft: evaluate.c:467: conflict_resolution_gen_dependency: Assertion `expr->payload.base == PROTO_BASE_LL_HDR' failed. + Aborted (core dumped) + + conflict_resolution_gen_dependency() can only handle linklayer + conflicts, hence the assert. + + Rename it accordingly. Also rename resolve_protocol_conflict, it doesn't + do anything for != PROTO_BASE_LL_HDR and extend the assertion to that + function too. + + Callers now enforce PROTO_BASE_LL_HDR prerequisite. + + after: + Error: conflicting transport layer protocols specified: comp vs. udp + ip6 nexthdr comp udp dport 4789 + ^^^^^^^^^ + + Signed-off-by: Florian Westphal + +Signed-off-by: Phil Sutter +--- + src/evaluate.c | 69 +++++++++---------- + ...solution_gen_dependency_base_ll_hdr_assert | 5 ++ + 2 files changed, 38 insertions(+), 36 deletions(-) + create mode 100644 tests/shell/testcases/bogons/nft-f/evaluate_conflict_resolution_gen_dependency_base_ll_hdr_assert + +diff --git a/src/evaluate.c b/src/evaluate.c +index 541299e..98f97bd 100644 +--- a/src/evaluate.c ++++ b/src/evaluate.c +@@ -494,9 +494,9 @@ int stmt_dependency_evaluate(struct eval_ctx *ctx, struct stmt *stmt) + } + + static int +-conflict_resolution_gen_dependency(struct eval_ctx *ctx, int protocol, +- const struct expr *expr, +- struct stmt **res) ++ll_conflict_resolution_gen_dependency(struct eval_ctx *ctx, int protocol, ++ const struct expr *expr, ++ struct stmt **res) + { + enum proto_bases base = expr->payload.base; + const struct proto_hdr_template *tmpl; +@@ -750,56 +750,52 @@ static bool proto_is_dummy(const struct proto_desc *desc) + return desc == &proto_inet || desc == &proto_netdev; + } + +-static int resolve_protocol_conflict(struct eval_ctx *ctx, +- const struct proto_desc *desc, +- struct expr *payload) ++static int resolve_ll_protocol_conflict(struct eval_ctx *ctx, ++ const struct proto_desc *desc, ++ struct expr *payload) + { + enum proto_bases base = payload->payload.base; + struct stmt *nstmt = NULL; + struct proto_ctx *pctx; ++ unsigned int i; + int link, err; + ++ assert(base == PROTO_BASE_LL_HDR); ++ + pctx = eval_proto_ctx(ctx); + +- if (payload->payload.base == PROTO_BASE_LL_HDR) { +- if (proto_is_dummy(desc)) { +- if (ctx->inner_desc) { +- proto_ctx_update(pctx, PROTO_BASE_LL_HDR, &payload->location, &proto_eth); +- } else { +- err = meta_iiftype_gen_dependency(ctx, payload, &nstmt); +- if (err < 0) +- return err; +- +- desc = payload->payload.desc; +- rule_stmt_insert_at(ctx->rule, nstmt, ctx->stmt); +- } ++ if (proto_is_dummy(desc)) { ++ if (ctx->inner_desc) { ++ proto_ctx_update(pctx, PROTO_BASE_LL_HDR, &payload->location, &proto_eth); + } else { +- unsigned int i; ++ err = meta_iiftype_gen_dependency(ctx, payload, &nstmt); ++ if (err < 0) ++ return err; + +- /* payload desc stored in the L2 header stack? No conflict. */ +- for (i = 0; i < pctx->stacked_ll_count; i++) { +- if (pctx->stacked_ll[i] == payload->payload.desc) +- return 0; +- } ++ desc = payload->payload.desc; ++ rule_stmt_insert_at(ctx->rule, nstmt, ctx->stmt); ++ } ++ } else { ++ unsigned int i; ++ ++ /* payload desc stored in the L2 header stack? No conflict. */ ++ for (i = 0; i < pctx->stacked_ll_count; i++) { ++ if (pctx->stacked_ll[i] == payload->payload.desc) ++ return 0; + } + } + +- assert(base <= PROTO_BASE_MAX); + /* This payload and the existing context don't match, conflict. */ + if (pctx->protocol[base + 1].desc != NULL) + return 1; + + link = proto_find_num(desc, payload->payload.desc); + if (link < 0 || +- conflict_resolution_gen_dependency(ctx, link, payload, &nstmt) < 0) ++ ll_conflict_resolution_gen_dependency(ctx, link, payload, &nstmt) < 0) + return 1; + +- if (base == PROTO_BASE_LL_HDR) { +- unsigned int i; +- +- for (i = 0; i < pctx->stacked_ll_count; i++) +- payload->payload.offset += pctx->stacked_ll[i]->length; +- } ++ for (i = 0; i < pctx->stacked_ll_count; i++) ++ payload->payload.offset += pctx->stacked_ll[i]->length; + + rule_stmt_insert_at(ctx->rule, nstmt, ctx->stmt); + +@@ -841,7 +837,7 @@ static int __expr_evaluate_payload(struct eval_ctx *ctx, struct expr *expr) + + link = proto_find_num(desc, payload->payload.desc); + if (link < 0 || +- conflict_resolution_gen_dependency(ctx, link, payload, &nstmt) < 0) ++ ll_conflict_resolution_gen_dependency(ctx, link, payload, &nstmt) < 0) + return expr_error(ctx->msgs, payload, + "conflicting protocols specified: %s vs. %s", + desc->name, +@@ -898,8 +894,8 @@ check_icmp: + /* If we already have context and this payload is on the same + * base, try to resolve the protocol conflict. + */ +- if (payload->payload.base == desc->base) { +- err = resolve_protocol_conflict(ctx, desc, payload); ++ if (base == PROTO_BASE_LL_HDR) { ++ err = resolve_ll_protocol_conflict(ctx, desc, payload); + if (err <= 0) + return err; + +@@ -908,7 +904,8 @@ check_icmp: + return 0; + } + return expr_error(ctx->msgs, payload, +- "conflicting protocols specified: %s vs. %s", ++ "conflicting %s protocols specified: %s vs. %s", ++ proto_base_names[base], + pctx->protocol[base].desc->name, + payload->payload.desc->name); + } +diff --git a/tests/shell/testcases/bogons/nft-f/evaluate_conflict_resolution_gen_dependency_base_ll_hdr_assert b/tests/shell/testcases/bogons/nft-f/evaluate_conflict_resolution_gen_dependency_base_ll_hdr_assert +new file mode 100644 +index 0000000..43d72c4 +--- /dev/null ++++ b/tests/shell/testcases/bogons/nft-f/evaluate_conflict_resolution_gen_dependency_base_ll_hdr_assert +@@ -0,0 +1,5 @@ ++table ip6 t { ++ chain c { ++ ip6 nexthdr comp udp dport 4789 ++ } ++} diff --git a/0068-evaluate-fix-check-for-unknown-in-cmd_op_to_name.patch b/0068-evaluate-fix-check-for-unknown-in-cmd_op_to_name.patch new file mode 100644 index 0000000..4e856f7 --- /dev/null +++ b/0068-evaluate-fix-check-for-unknown-in-cmd_op_to_name.patch @@ -0,0 +1,48 @@ +From e53d46d90f0b73be34c42abfd759b16306da03ac Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:42 +0200 +Subject: [PATCH] evaluate: fix check for unknown in cmd_op_to_name +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 9f76bb63c0c706ea5c0d55931ee690ca5dccaf16 + +commit 9f76bb63c0c706ea5c0d55931ee690ca5dccaf16 +Author: 谢致邦 (XIE Zhibang) +Date: Wed Feb 7 15:10:20 2024 +0000 + + evaluate: fix check for unknown in cmd_op_to_name + + Example: + nft --debug=all destroy table ip missingtable + + Before: + Evaluate unknown + + After: + Evaluate destroy + + Fixes: e1dfd5cc4c46 ("src: add support to command "destroy"") + Signed-off-by: 谢致邦 (XIE Zhibang) + Signed-off-by: Phil Sutter + +Signed-off-by: Phil Sutter +--- + src/evaluate.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/evaluate.c b/src/evaluate.c +index 98f97bd..20950fd 100644 +--- a/src/evaluate.c ++++ b/src/evaluate.c +@@ -5886,7 +5886,7 @@ static const char * const cmd_op_name[] = { + + static const char *cmd_op_to_name(enum cmd_ops op) + { +- if (op > CMD_DESCRIBE) ++ if (op >= array_size(cmd_op_name)) + return "unknown"; + + return cmd_op_name[op]; diff --git a/0069-cache-Optimize-caching-for-list-tables-command.patch b/0069-cache-Optimize-caching-for-list-tables-command.patch new file mode 100644 index 0000000..3d3df22 --- /dev/null +++ b/0069-cache-Optimize-caching-for-list-tables-command.patch @@ -0,0 +1,49 @@ +From 5bc0de5ef742c4746ca45a8940998fcfeef29b6f Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:42 +0200 +Subject: [PATCH] cache: Optimize caching for 'list tables' command + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 674eb7fa2895813b25f6fbfcc9417fc0788fade1 + +commit 674eb7fa2895813b25f6fbfcc9417fc0788fade1 +Author: Phil Sutter +Date: Tue Feb 6 19:26:57 2024 +0100 + + cache: Optimize caching for 'list tables' command + + No point in fetching anything other than existing tables from kernel: + 'list tables' merely prints existing table names, no contents. + + Also populate filter's family field to reduce overhead when listing + tables in one family with many tables in another one. It works without + further adjustments because nftnl_nlmsg_build_hdr() will use the value + for nfgen_family. + + Reported-by: anton.khazan@gmail.com + Link: https://bugzilla.netfilter.org/show_bug.cgi?id=1735 + Signed-off-by: Phil Sutter + +Signed-off-by: Phil Sutter +--- + src/cache.c | 6 +++++- + 1 file changed, 5 insertions(+), 1 deletion(-) + +diff --git a/src/cache.c b/src/cache.c +index a3cd795..c000e32 100644 +--- a/src/cache.c ++++ b/src/cache.c +@@ -203,8 +203,12 @@ static unsigned int evaluate_cache_list(struct nft_ctx *nft, struct cmd *cmd, + { + switch (cmd->obj) { + case CMD_OBJ_TABLE: +- if (filter && cmd->handle.table.name) { ++ if (filter) + filter->list.family = cmd->handle.family; ++ if (!cmd->handle.table.name) { ++ flags |= NFT_CACHE_TABLE; ++ break; ++ } else if (filter) { + filter->list.table = cmd->handle.table.name; + } + flags |= NFT_CACHE_FULL; diff --git a/0070-evaluate-skip-byteorder-conversion-for-selector-smal.patch b/0070-evaluate-skip-byteorder-conversion-for-selector-smal.patch new file mode 100644 index 0000000..5c0a681 --- /dev/null +++ b/0070-evaluate-skip-byteorder-conversion-for-selector-smal.patch @@ -0,0 +1,209 @@ +From ff07e944ab10459c7fa9b6beac5c622e48eaa2f5 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:42 +0200 +Subject: [PATCH] evaluate: skip byteorder conversion for selector smaller than + 2 bytes + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 9fe58952c45a1643dc7df1a0b1f5d88e8ae1a978 + +commit 9fe58952c45a1643dc7df1a0b1f5d88e8ae1a978 +Author: Pablo Neira Ayuso +Date: Wed Feb 7 23:53:32 2024 +0100 + + evaluate: skip byteorder conversion for selector smaller than 2 bytes + + Add unary expression to trigger byteorder conversion for host byteorder + selectors only if selectors length is larger or equal than 2 bytes. + + # cat test.nft + table ip x { + set test { + type ipv4_addr . ether_addr . inet_proto + flags interval + } + + chain y { + ip saddr . ether saddr . meta l4proto @test counter + } + } + + # nft -f test.nft + ip x y + [ meta load iiftype => reg 1 ] + [ cmp eq reg 1 0x00000001 ] + [ payload load 4b @ network header + 12 => reg 1 ] + [ payload load 6b @ link header + 6 => reg 9 ] + [ meta load l4proto => reg 11 ] + [ byteorder reg 11 = hton(reg 11, 2, 1) ] <--- should not be here + [ lookup reg 1 set test ] + [ counter pkts 0 bytes 0 ] + + Fixes: 1017d323cafa ("src: support for selectors with different byteorder with interval concatenations") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/evaluate.c | 12 ++++++---- + tests/py/inet/meta.t | 1 + + tests/py/inet/meta.t.json | 41 ++++++++++++++++++++++++++++++++ + tests/py/inet/meta.t.json.output | 41 ++++++++++++++++++++++++++++++++ + tests/py/inet/meta.t.payload | 14 +++++++++++ + 5 files changed, 104 insertions(+), 5 deletions(-) + +diff --git a/src/evaluate.c b/src/evaluate.c +index 20950fd..e64bc91 100644 +--- a/src/evaluate.c ++++ b/src/evaluate.c +@@ -194,12 +194,14 @@ static int byteorder_conversion(struct eval_ctx *ctx, struct expr **expr, + + assert(basetype == TYPE_INTEGER); + +- op = byteorder_conversion_op(i, byteorder); +- unary = unary_expr_alloc(&i->location, op, i); +- if (expr_evaluate(ctx, &unary) < 0) +- return -1; ++ if (div_round_up(i->len, BITS_PER_BYTE) >= 2) { ++ op = byteorder_conversion_op(i, byteorder); ++ unary = unary_expr_alloc(&i->location, op, i); ++ if (expr_evaluate(ctx, &unary) < 0) ++ return -1; + +- list_replace(&i->list, &unary->list); ++ list_replace(&i->list, &unary->list); ++ } + } + + return 0; +diff --git a/tests/py/inet/meta.t b/tests/py/inet/meta.t +index 5c062b3..7d2515c 100644 +--- a/tests/py/inet/meta.t ++++ b/tests/py/inet/meta.t +@@ -25,6 +25,7 @@ meta mark set ct mark >> 8;ok + meta mark . tcp dport { 0x0000000a-0x00000014 . 80-90, 0x00100000-0x00100123 . 100-120 };ok + ip saddr . meta mark { 1.2.3.4 . 0x00000100 , 1.2.3.6-1.2.3.8 . 0x00000200-0x00000300 };ok + ip saddr . meta mark { 1.2.3.4 . 0x00000100 , 5.6.7.8 . 0x00000200 };ok ++ip saddr . ether saddr . meta l4proto { 1.2.3.4 . aa:bb:cc:dd:ee:ff . 6 };ok + + meta mark set ip dscp;ok + meta mark set ip dscp | 0x40;ok +diff --git a/tests/py/inet/meta.t.json b/tests/py/inet/meta.t.json +index 3ba0fd1..0fee165 100644 +--- a/tests/py/inet/meta.t.json ++++ b/tests/py/inet/meta.t.json +@@ -526,3 +526,44 @@ + } + ] + ++# ip saddr . ether saddr . meta l4proto { 1.2.3.4 . aa:bb:cc:dd:ee:ff . 6 } ++[ ++ { ++ "match": { ++ "left": { ++ "concat": [ ++ { ++ "payload": { ++ "field": "saddr", ++ "protocol": "ip" ++ } ++ }, ++ { ++ "payload": { ++ "field": "saddr", ++ "protocol": "ether" ++ } ++ }, ++ { ++ "meta": { ++ "key": "l4proto" ++ } ++ } ++ ] ++ }, ++ "op": "==", ++ "right": { ++ "set": [ ++ { ++ "concat": [ ++ "1.2.3.4", ++ "aa:bb:cc:dd:ee:ff", ++ "tcp" ++ ] ++ } ++ ] ++ } ++ } ++ } ++] ++ +diff --git a/tests/py/inet/meta.t.json.output b/tests/py/inet/meta.t.json.output +index 3e7dd21..8697d5a 100644 +--- a/tests/py/inet/meta.t.json.output ++++ b/tests/py/inet/meta.t.json.output +@@ -51,3 +51,44 @@ + } + ] + ++# ip saddr . ether saddr . meta l4proto { 1.2.3.4 . aa:bb:cc:dd:ee:ff . 6 } ++[ ++ { ++ "match": { ++ "left": { ++ "concat": [ ++ { ++ "payload": { ++ "field": "saddr", ++ "protocol": "ip" ++ } ++ }, ++ { ++ "payload": { ++ "field": "saddr", ++ "protocol": "ether" ++ } ++ }, ++ { ++ "meta": { ++ "key": "l4proto" ++ } ++ } ++ ] ++ }, ++ "op": "==", ++ "right": { ++ "set": [ ++ { ++ "concat": [ ++ "1.2.3.4", ++ "aa:bb:cc:dd:ee:ff", ++ 6 ++ ] ++ } ++ ] ++ } ++ } ++ } ++] ++ +diff --git a/tests/py/inet/meta.t.payload b/tests/py/inet/meta.t.payload +index c53b507..7184fa0 100644 +--- a/tests/py/inet/meta.t.payload ++++ b/tests/py/inet/meta.t.payload +@@ -173,3 +173,17 @@ inet test-inet input + [ bitwise reg 1 = ( reg 1 & 0xffffffbf ) ^ 0x00000040 ] + [ meta set mark with reg 1 ] + ++# ip saddr . ether saddr . meta l4proto { 1.2.3.4 . aa:bb:cc:dd:ee:ff . 6 } ++__set%d test-inet 3 size 1 ++__set%d test-inet 0 ++ element 04030201 ddccbbaa 0000ffee 00000006 : 0 [end] ++inet test-inet input ++ [ meta load nfproto => reg 1 ] ++ [ cmp eq reg 1 0x00000002 ] ++ [ meta load iiftype => reg 1 ] ++ [ cmp eq reg 1 0x00000001 ] ++ [ payload load 4b @ network header + 12 => reg 1 ] ++ [ payload load 6b @ link header + 6 => reg 9 ] ++ [ meta load l4proto => reg 11 ] ++ [ lookup reg 1 set __set%d ] ++ diff --git a/0071-netlink_delinearize-move-concat-and-value-postproces.patch b/0071-netlink_delinearize-move-concat-and-value-postproces.patch new file mode 100644 index 0000000..37cf89d --- /dev/null +++ b/0071-netlink_delinearize-move-concat-and-value-postproces.patch @@ -0,0 +1,132 @@ +From 633d5e7cf14961687216422323b6476775e0086e Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:42 +0200 +Subject: [PATCH] netlink_delinearize: move concat and value postprocessing to + helpers + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 7ef933d16fd1b14afb276ab26f4114a2c9d6a3ea + +commit 7ef933d16fd1b14afb276ab26f4114a2c9d6a3ea +Author: Florian Westphal +Date: Fri Jan 19 13:47:08 2024 +0100 + + netlink_delinearize: move concat and value postprocessing to helpers + + No functional changes intended. + + Signed-off-by: Florian Westphal + +Signed-off-by: Phil Sutter +--- + src/netlink_delinearize.c | 82 ++++++++++++++++++++++----------------- + 1 file changed, 47 insertions(+), 35 deletions(-) + +diff --git a/src/netlink_delinearize.c b/src/netlink_delinearize.c +index e214510..27630a8 100644 +--- a/src/netlink_delinearize.c ++++ b/src/netlink_delinearize.c +@@ -2720,6 +2720,50 @@ static struct expr *expr_postprocess_string(struct expr *expr) + return out; + } + ++static void expr_postprocess_value(struct rule_pp_ctx *ctx, struct expr **exprp) ++{ ++ struct expr *expr = *exprp; ++ ++ // FIXME ++ if (expr->byteorder == BYTEORDER_HOST_ENDIAN) ++ mpz_switch_byteorder(expr->value, expr->len / BITS_PER_BYTE); ++ ++ if (expr_basetype(expr)->type == TYPE_STRING) ++ *exprp = expr_postprocess_string(expr); ++ ++ expr = *exprp; ++ if (expr->dtype->basetype != NULL && ++ expr->dtype->basetype->type == TYPE_BITMASK) ++ *exprp = bitmask_expr_to_binops(expr); ++} ++ ++static void expr_postprocess_concat(struct rule_pp_ctx *ctx, struct expr **exprp) ++{ ++ struct expr *i, *n, *expr = *exprp; ++ unsigned int type = expr->dtype->type, ntype = 0; ++ int off = expr->dtype->subtypes; ++ const struct datatype *dtype; ++ LIST_HEAD(tmp); ++ ++ assert(expr->etype == EXPR_CONCAT); ++ ++ ctx->flags |= RULE_PP_IN_CONCATENATION; ++ list_for_each_entry_safe(i, n, &expr->expressions, list) { ++ if (type) { ++ dtype = concat_subtype_lookup(type, --off); ++ expr_set_type(i, dtype, dtype->byteorder); ++ } ++ list_del(&i->list); ++ expr_postprocess(ctx, &i); ++ list_add_tail(&i->list, &tmp); ++ ++ ntype = concat_subtype_add(ntype, i->dtype->type); ++ } ++ ctx->flags &= ~RULE_PP_IN_CONCATENATION; ++ list_splice(&tmp, &expr->expressions); ++ __datatype_set(expr, concat_type_alloc(ntype)); ++} ++ + static void expr_postprocess(struct rule_pp_ctx *ctx, struct expr **exprp) + { + struct dl_proto_ctx *dl = dl_proto_ctx(ctx); +@@ -2746,30 +2790,9 @@ static void expr_postprocess(struct rule_pp_ctx *ctx, struct expr **exprp) + list_for_each_entry(i, &expr->expressions, list) + expr_postprocess(ctx, &i); + break; +- case EXPR_CONCAT: { +- unsigned int type = expr->dtype->type, ntype = 0; +- int off = expr->dtype->subtypes; +- const struct datatype *dtype; +- LIST_HEAD(tmp); +- struct expr *n; +- +- ctx->flags |= RULE_PP_IN_CONCATENATION; +- list_for_each_entry_safe(i, n, &expr->expressions, list) { +- if (type) { +- dtype = concat_subtype_lookup(type, --off); +- expr_set_type(i, dtype, dtype->byteorder); +- } +- list_del(&i->list); +- expr_postprocess(ctx, &i); +- list_add_tail(&i->list, &tmp); +- +- ntype = concat_subtype_add(ntype, i->dtype->type); +- } +- ctx->flags &= ~RULE_PP_IN_CONCATENATION; +- list_splice(&tmp, &expr->expressions); +- __datatype_set(expr, concat_type_alloc(ntype)); ++ case EXPR_CONCAT: ++ expr_postprocess_concat(ctx, exprp); + break; +- } + case EXPR_UNARY: + expr_postprocess(ctx, &expr->arg); + expr_set_type(expr, expr->arg->dtype, !expr->arg->byteorder); +@@ -2882,18 +2905,7 @@ static void expr_postprocess(struct rule_pp_ctx *ctx, struct expr **exprp) + payload_dependency_kill(&dl->pdctx, expr, dl->pctx.family); + break; + case EXPR_VALUE: +- // FIXME +- if (expr->byteorder == BYTEORDER_HOST_ENDIAN) +- mpz_switch_byteorder(expr->value, expr->len / BITS_PER_BYTE); +- +- if (expr_basetype(expr)->type == TYPE_STRING) +- *exprp = expr_postprocess_string(expr); +- +- expr = *exprp; +- if (expr->dtype->basetype != NULL && +- expr->dtype->basetype->type == TYPE_BITMASK) +- *exprp = bitmask_expr_to_binops(expr); +- ++ expr_postprocess_value(ctx, exprp); + break; + case EXPR_RANGE: + expr_postprocess(ctx, &expr->left); diff --git a/0072-expression-missing-line-in-describe-command-with-inv.patch b/0072-expression-missing-line-in-describe-command-with-inv.patch new file mode 100644 index 0000000..8db79d5 --- /dev/null +++ b/0072-expression-missing-line-in-describe-command-with-inv.patch @@ -0,0 +1,50 @@ +From 7390323f695abb24d5886f671e4faccd8d44f1eb Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:42 +0200 +Subject: [PATCH] expression: missing line in describe command with invalid + expression + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 2b24dd29c5fa1c7e4cf44f0753752d25106273a0 + +commit 2b24dd29c5fa1c7e4cf44f0753752d25106273a0 +Author: Pablo Neira Ayuso +Date: Tue Feb 13 17:09:20 2024 +0100 + + expression: missing line in describe command with invalid expression + + Before: + + duh@testbed:~# nft describe blah + symbol expression, datatype invalid (invalid)duh@testbed:# + + After: + + duh@testbed:~# nft describe blah + symbol expression, datatype invalid (invalid) + duh@testbed:# + + Fixes: 48aca2de80a7 ("iptopt: fix crash with invalid field/type combo") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/expression.c | 4 +++- + 1 file changed, 3 insertions(+), 1 deletion(-) + +diff --git a/src/expression.c b/src/expression.c +index dde48b6..cb2573f 100644 +--- a/src/expression.c ++++ b/src/expression.c +@@ -140,8 +140,10 @@ void expr_describe(const struct expr *expr, struct output_ctx *octx) + nft_print(octx, "%s expression, datatype %s (%s)", + expr_name(expr), dtype->name, dtype->desc); + +- if (dtype == &invalid_type) ++ if (dtype == &invalid_type) { ++ nft_print(octx, "\n"); + return; ++ } + } + + if (dtype->basetype != NULL) { diff --git a/0073-evaluate-permit-use-of-host-endian-constant-values-i.patch b/0073-evaluate-permit-use-of-host-endian-constant-values-i.patch new file mode 100644 index 0000000..97dc8d4 --- /dev/null +++ b/0073-evaluate-permit-use-of-host-endian-constant-values-i.patch @@ -0,0 +1,137 @@ +From 20e6758f2cbf8c42e3c58ec10207b12c490c8fb9 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:42 +0200 +Subject: [PATCH] evaluate: permit use of host-endian constant values in set + lookup keys + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit c0080feb0d034913409944d23873cce4bf9edf9e + +commit c0080feb0d034913409944d23873cce4bf9edf9e +Author: Pablo Neira Ayuso +Date: Mon Feb 12 16:46:29 2024 +0100 + + evaluate: permit use of host-endian constant values in set lookup keys + + AFL found following crash: + + table ip filter { + map ipsec_in { + typeof ipsec in reqid . iif : verdict + flags interval + } + + chain INPUT { + type filter hook input priority filter; policy drop; + ipsec in reqid . 100 @ipsec_in + } + } + + Which yields: + nft: evaluate.c:1213: expr_evaluate_unary: Assertion `!expr_is_constant(arg)' failed. + + All existing test cases with constant values use big endian values, but + "iif" expects host endian values. + + As raw values were not supported before, concat byteorder conversion + doesn't handle constants. + + Fix this: + + 1. Add constant handling so that the number is converted in-place, + without unary expression. + + 2. Add the inverse handling on delinearization for non-interval set + types. + When dissecting the concat data soup, watch for integer constants where + the datatype indicates host endian integer. + + Last, extend an existing test case with the afl input to cover + in/output. + + A new test case is added to test linearization, delinearization and + matching. + + Based on original patch from Florian Westphal, patch subject and + description wrote by him. + + Fixes: b422b07ab2f9 ("src: permit use of constant values in set lookup keys") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + include/netlink.h | 1 + + src/evaluate.c | 19 +++++++++++++------ + src/netlink_delinearize.c | 5 ++++- + 3 files changed, 18 insertions(+), 7 deletions(-) + +diff --git a/include/netlink.h b/include/netlink.h +index 6766d7e..2ce4b39 100644 +--- a/include/netlink.h ++++ b/include/netlink.h +@@ -61,6 +61,7 @@ struct rule_pp_ctx { + struct dl_proto_ctx *dl; + struct stmt *stmt; + unsigned int flags; ++ struct set *set; + }; + + extern const struct input_descriptor indesc_netlink; +diff --git a/src/evaluate.c b/src/evaluate.c +index e64bc91..89656eb 100644 +--- a/src/evaluate.c ++++ b/src/evaluate.c +@@ -194,13 +194,20 @@ static int byteorder_conversion(struct eval_ctx *ctx, struct expr **expr, + + assert(basetype == TYPE_INTEGER); + +- if (div_round_up(i->len, BITS_PER_BYTE) >= 2) { +- op = byteorder_conversion_op(i, byteorder); +- unary = unary_expr_alloc(&i->location, op, i); +- if (expr_evaluate(ctx, &unary) < 0) +- return -1; ++ switch (i->etype) { ++ case EXPR_VALUE: ++ if (i->byteorder == BYTEORDER_HOST_ENDIAN) ++ mpz_switch_byteorder(i->value, div_round_up(i->len, BITS_PER_BYTE)); ++ break; ++ default: ++ if (div_round_up(i->len, BITS_PER_BYTE) >= 2) { ++ op = byteorder_conversion_op(i, byteorder); ++ unary = unary_expr_alloc(&i->location, op, i); ++ if (expr_evaluate(ctx, &unary) < 0) ++ return -1; + +- list_replace(&i->list, &unary->list); ++ list_replace(&i->list, &unary->list); ++ } + } + } + +diff --git a/src/netlink_delinearize.c b/src/netlink_delinearize.c +index 27630a8..1d30a78 100644 +--- a/src/netlink_delinearize.c ++++ b/src/netlink_delinearize.c +@@ -2722,10 +2722,11 @@ static struct expr *expr_postprocess_string(struct expr *expr) + + static void expr_postprocess_value(struct rule_pp_ctx *ctx, struct expr **exprp) + { ++ bool interval = (ctx->set && ctx->set->flags & NFT_SET_INTERVAL); + struct expr *expr = *exprp; + + // FIXME +- if (expr->byteorder == BYTEORDER_HOST_ENDIAN) ++ if (expr->byteorder == BYTEORDER_HOST_ENDIAN && !interval) + mpz_switch_byteorder(expr->value, expr->len / BITS_PER_BYTE); + + if (expr_basetype(expr)->type == TYPE_STRING) +@@ -2869,7 +2870,9 @@ static void expr_postprocess(struct rule_pp_ctx *ctx, struct expr **exprp) + + datatype_set(expr->left, expr->right->dtype); + } ++ ctx->set = expr->right->set; + expr_postprocess(ctx, &expr->left); ++ ctx->set = NULL; + break; + default: + expr_postprocess(ctx, &expr->left); diff --git a/0074-tests-shell-permit-use-of-host-endian-constant-value.patch b/0074-tests-shell-permit-use-of-host-endian-constant-value.patch new file mode 100644 index 0000000..d41de31 --- /dev/null +++ b/0074-tests-shell-permit-use-of-host-endian-constant-value.patch @@ -0,0 +1,179 @@ +From e5d1dce48ec1e1b4e0f0a4ec552ca6a5af207ddc Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:42 +0200 +Subject: [PATCH] tests: shell: permit use of host-endian constant values in + set lookup keys + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 40afa4bb2c7c7c8d488a8d44830c6b72cd98c640 + +commit 40afa4bb2c7c7c8d488a8d44830c6b72cd98c640 +Author: Florian Westphal +Date: Wed Feb 14 11:41:30 2024 +0100 + + tests: shell: permit use of host-endian constant values in set lookup keys + + extend an existing test case with the afl input to cover in/output. + + A new test case is added to test linearization, delinearization and + matching + + Fixes: c0080feb0d03 ("evaluate: permit use of host-endian constant values in set lookup keys") + Signed-off-by: Florian Westphal + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + .../packetpath/dumps/set_lookups.nft | 51 +++++++++++++++ + tests/shell/testcases/packetpath/set_lookups | 64 +++++++++++++++++++ + .../sets/dumps/typeof_sets_concat.nft | 11 ++++ + 3 files changed, 126 insertions(+) + create mode 100644 tests/shell/testcases/packetpath/dumps/set_lookups.nft + create mode 100755 tests/shell/testcases/packetpath/set_lookups + +diff --git a/tests/shell/testcases/packetpath/dumps/set_lookups.nft b/tests/shell/testcases/packetpath/dumps/set_lookups.nft +new file mode 100644 +index 0000000..7566f55 +--- /dev/null ++++ b/tests/shell/testcases/packetpath/dumps/set_lookups.nft +@@ -0,0 +1,51 @@ ++table ip t { ++ set s { ++ type ipv4_addr . iface_index ++ flags interval ++ elements = { 127.0.0.1 . "lo", ++ 127.0.0.2 . "lo" } ++ } ++ ++ set s2 { ++ typeof ip saddr . iif ++ elements = { 127.0.0.1 . "lo", ++ 127.0.0.2 . "lo" } ++ } ++ ++ set s3 { ++ type iface_index ++ elements = { "lo" } ++ } ++ ++ set s4 { ++ type iface_index ++ flags interval ++ elements = { "lo" } ++ } ++ ++ set nomatch { ++ typeof ip saddr . iif ++ elements = { 127.0.0.3 . "lo" } ++ } ++ ++ set nomatch2 { ++ type ipv4_addr . iface_index ++ elements = { 127.0.0.2 . 90000 } ++ } ++ ++ chain c { ++ type filter hook input priority filter; policy accept; ++ icmp type echo-request ip saddr . iif @s counter packets 1 bytes 84 ++ icmp type echo-request ip saddr . "lo" @s counter packets 1 bytes 84 ++ icmp type echo-request ip saddr . "lo" @s counter packets 1 bytes 84 ++ icmp type echo-request ip saddr . iif @s2 counter packets 1 bytes 84 ++ icmp type echo-request ip saddr . "lo" @s2 counter packets 1 bytes 84 ++ icmp type echo-request ip saddr . "lo" @s2 counter packets 1 bytes 84 ++ icmp type echo-request ip daddr . "lo" @s counter packets 1 bytes 84 ++ icmp type echo-request ip daddr . "lo" @s2 counter packets 1 bytes 84 ++ icmp type echo-request iif @s3 counter packets 1 bytes 84 ++ icmp type echo-request iif @s4 counter packets 1 bytes 84 ++ ip daddr . "lo" @nomatch counter packets 0 bytes 0 drop ++ ip daddr . iif @nomatch2 counter packets 0 bytes 0 drop ++ } ++} +diff --git a/tests/shell/testcases/packetpath/set_lookups b/tests/shell/testcases/packetpath/set_lookups +new file mode 100755 +index 0000000..84a0000 +--- /dev/null ++++ b/tests/shell/testcases/packetpath/set_lookups +@@ -0,0 +1,64 @@ ++#!/bin/bash ++ ++set -e ++ ++$NFT -f /dev/stdin <<"EOF" ++table ip t { ++ set s { ++ type ipv4_addr . iface_index ++ flags interval ++ elements = { 127.0.0.1 . 1 } ++ } ++ ++ set s2 { ++ typeof ip saddr . meta iif ++ elements = { 127.0.0.1 . 1 } ++ } ++ ++ set s3 { ++ type iface_index ++ elements = { "lo" } ++ } ++ ++ set s4 { ++ type iface_index ++ flags interval ++ elements = { "lo" } ++ } ++ ++ set nomatch { ++ typeof ip saddr . meta iif ++ elements = { 127.0.0.3 . 1 } ++ } ++ ++ set nomatch2 { ++ type ipv4_addr . iface_index ++ elements = { 127.0.0.2 . 90000 } ++ } ++ ++ chain c { ++ type filter hook input priority filter; ++ icmp type echo-request ip saddr . meta iif @s counter ++ icmp type echo-request ip saddr . 1 @s counter ++ icmp type echo-request ip saddr . "lo" @s counter ++ icmp type echo-request ip saddr . meta iif @s2 counter ++ icmp type echo-request ip saddr . 1 @s2 counter ++ icmp type echo-request ip saddr . "lo" @s2 counter ++ ++ icmp type echo-request ip daddr . "lo" @s counter ++ icmp type echo-request ip daddr . "lo" @s2 counter ++ ++ icmp type echo-request meta iif @s3 counter ++ icmp type echo-request meta iif @s4 counter ++ ++ ip daddr . 1 @nomatch counter drop ++ ip daddr . meta iif @nomatch2 counter drop ++ } ++} ++EOF ++ ++$NFT add element t s { 127.0.0.2 . 1 } ++$NFT add element t s2 { 127.0.0.2 . "lo" } ++ ++ip link set lo up ++ping -q -c 1 127.0.0.2 > /dev/null +diff --git a/tests/shell/testcases/sets/dumps/typeof_sets_concat.nft b/tests/shell/testcases/sets/dumps/typeof_sets_concat.nft +index dbaf7cd..348b584 100644 +--- a/tests/shell/testcases/sets/dumps/typeof_sets_concat.nft ++++ b/tests/shell/testcases/sets/dumps/typeof_sets_concat.nft +@@ -10,3 +10,14 @@ table netdev t { + ether type != 8021q update @s { ether daddr . 123 timeout 1m } counter packets 0 bytes 0 return + } + } ++table ip t { ++ set s { ++ typeof ipsec in reqid . iif ++ size 16 ++ flags interval ++ } ++ ++ chain c2 { ++ ipsec in reqid . "lo" @s ++ } ++} diff --git a/0075-src-improve-error-reporting-for-destroy-command.patch b/0075-src-improve-error-reporting-for-destroy-command.patch new file mode 100644 index 0000000..925e7f5 --- /dev/null +++ b/0075-src-improve-error-reporting-for-destroy-command.patch @@ -0,0 +1,53 @@ +From 1a81b2faa37e30603561d4b10ac007dc29e509b1 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:42 +0200 +Subject: [PATCH] src: improve error reporting for destroy command +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit f27f032b8da584d4447dc03b3ccd3370433e2c0f + +commit f27f032b8da584d4447dc03b3ccd3370433e2c0f +Author: 谢致邦 (XIE Zhibang) +Date: Wed Feb 21 15:17:09 2024 +0000 + + src: improve error reporting for destroy command + + Example for older kernels (<6.3): + nft destroy table ip missingtable + + Before: + Error: Could not process rule: Invalid argument + + After: + Error: "destroy" command is not supported, perhaps kernel support is + missing? + + Fixes: e1dfd5cc4c46 ("src: add support to command "destroy"") + Signed-off-by: 谢致邦 (XIE Zhibang) + Signed-off-by: Florian Westphal + +Signed-off-by: Phil Sutter +--- + src/cmd.c | 6 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/src/cmd.c b/src/cmd.c +index 68c476c..21533e0 100644 +--- a/src/cmd.c ++++ b/src/cmd.c +@@ -311,6 +311,12 @@ void nft_cmd_error(struct netlink_ctx *ctx, struct cmd *cmd, + break; + } + ++ if (cmd->op == CMD_DESTROY && err->err == EINVAL) { ++ netlink_io_error(ctx, loc, ++ "\"destroy\" command is not supported, perhaps kernel support is missing?"); ++ return; ++ } ++ + netlink_io_error(ctx, loc, "Could not process rule: %s", + strerror(err->err)); + } diff --git a/0076-parser-compact-interval-typeof-rules.patch b/0076-parser-compact-interval-typeof-rules.patch new file mode 100644 index 0000000..a567df5 --- /dev/null +++ b/0076-parser-compact-interval-typeof-rules.patch @@ -0,0 +1,92 @@ +From c9d37a13faaa05ee0d260b3828bc25e612ffae20 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:42 +0200 +Subject: [PATCH] parser: compact interval typeof rules + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 81fc7aee0d523d61077518534036ff10beddb3e9 + +commit 81fc7aee0d523d61077518534036ff10beddb3e9 +Author: Florian Westphal +Date: Tue Feb 27 15:50:05 2024 +0100 + + parser: compact interval typeof rules + + There are two nearly identical blocks for typeof maps: + one with INTERVAL keyword present and one without. + + Compact this into a single block. + + Signed-off-by: Florian Westphal + +Signed-off-by: Phil Sutter +--- + src/parser_bison.y | 34 ++++++++++++++-------------------- + 1 file changed, 14 insertions(+), 20 deletions(-) + +diff --git a/src/parser_bison.y b/src/parser_bison.y +index e81336f..82aac8d 100644 +--- a/src/parser_bison.y ++++ b/src/parser_bison.y +@@ -812,8 +812,8 @@ int nft_lex(void *, void *, void *); + + %type symbol_expr verdict_expr integer_expr variable_expr chain_expr policy_expr + %destructor { expr_free($$); } symbol_expr verdict_expr integer_expr variable_expr chain_expr policy_expr +-%type primary_expr shift_expr and_expr typeof_expr typeof_data_expr +-%destructor { expr_free($$); } primary_expr shift_expr and_expr typeof_expr typeof_data_expr ++%type primary_expr shift_expr and_expr typeof_expr typeof_data_expr typeof_verdict_expr ++%destructor { expr_free($$); } primary_expr shift_expr and_expr typeof_expr typeof_data_expr typeof_verdict_expr + %type exclusive_or_expr inclusive_or_expr + %destructor { expr_free($$); } exclusive_or_expr inclusive_or_expr + %type basic_expr +@@ -2115,7 +2115,7 @@ subchain_block : /* empty */ { $$ = $-1; } + } + ; + +-typeof_data_expr : primary_expr ++typeof_verdict_expr : primary_expr + { + struct expr *e = $1; + +@@ -2147,6 +2147,17 @@ typeof_data_expr : primary_expr + } + ; + ++typeof_data_expr : INTERVAL typeof_expr ++ { ++ $2->flags |= EXPR_F_INTERVAL; ++ $$ = $2; ++ } ++ | typeof_verdict_expr ++ { ++ $$ = $1; ++ } ++ ; ++ + typeof_expr : primary_expr + { + if (expr_ops($1)->build_udata == NULL) { +@@ -2326,23 +2337,6 @@ map_block : /* empty */ { $$ = $-1; } + $1->flags |= NFT_SET_MAP; + $$ = $1; + } +- | map_block TYPEOF +- typeof_expr COLON INTERVAL typeof_expr +- stmt_separator +- { +- if (already_set($1->key, &@2, state)) { +- expr_free($3); +- expr_free($6); +- YYERROR; +- } +- +- $1->key = $3; +- $1->data = $6; +- $1->data->flags |= EXPR_F_INTERVAL; +- +- $1->flags |= NFT_SET_MAP; +- $$ = $1; +- } + | map_block TYPE + data_type_expr COLON map_block_obj_type + stmt_separator close_scope_type diff --git a/0077-parser-compact-type-typeof-set-rules.patch b/0077-parser-compact-type-typeof-set-rules.patch new file mode 100644 index 0000000..094af43 --- /dev/null +++ b/0077-parser-compact-type-typeof-set-rules.patch @@ -0,0 +1,78 @@ +From 6bc395ed503556ef05e3db62926226afd6b036a9 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:42 +0200 +Subject: [PATCH] parser: compact type/typeof set rules + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit fb98cb91c575880617e5da2cb6f094525516cb78 + +commit fb98cb91c575880617e5da2cb6f094525516cb78 +Author: Florian Westphal +Date: Tue Feb 27 15:53:19 2024 +0100 + + parser: compact type/typeof set rules + + Set/maps keys can be declared either by 'type' or 'typeof' keyword. + + Compact this to use a common block for both cases. + + The datatype_set call is redundant, remove it: + at this point $3 == $1->key, so this is a no-op. + + Signed-off-by: Florian Westphal + +Signed-off-by: Phil Sutter +--- + src/parser_bison.y | 24 +++++++++--------------- + 1 file changed, 9 insertions(+), 15 deletions(-) + +diff --git a/src/parser_bison.y b/src/parser_bison.y +index 82aac8d..23b64ce 100644 +--- a/src/parser_bison.y ++++ b/src/parser_bison.y +@@ -812,8 +812,8 @@ int nft_lex(void *, void *, void *); + + %type symbol_expr verdict_expr integer_expr variable_expr chain_expr policy_expr + %destructor { expr_free($$); } symbol_expr verdict_expr integer_expr variable_expr chain_expr policy_expr +-%type primary_expr shift_expr and_expr typeof_expr typeof_data_expr typeof_verdict_expr +-%destructor { expr_free($$); } primary_expr shift_expr and_expr typeof_expr typeof_data_expr typeof_verdict_expr ++%type primary_expr shift_expr and_expr typeof_expr typeof_data_expr typeof_key_expr typeof_verdict_expr ++%destructor { expr_free($$); } primary_expr shift_expr and_expr typeof_expr typeof_data_expr typeof_key_expr typeof_verdict_expr + %type exclusive_or_expr inclusive_or_expr + %destructor { expr_free($$); } exclusive_or_expr inclusive_or_expr + %type basic_expr +@@ -2187,27 +2187,21 @@ set_block_alloc : /* empty */ + } + ; + ++typeof_key_expr : TYPEOF typeof_expr { $$ = $2; } ++ | TYPE data_type_expr close_scope_type { $$ = $2; } ++ ; ++ + set_block : /* empty */ { $$ = $-1; } + | set_block common_block + | set_block stmt_separator +- | set_block TYPE data_type_expr stmt_separator close_scope_type ++ | set_block typeof_key_expr stmt_separator + { + if (already_set($1->key, &@2, state)) { +- expr_free($3); ++ expr_free($2); + YYERROR; + } + +- $1->key = $3; +- $$ = $1; +- } +- | set_block TYPEOF typeof_expr stmt_separator +- { +- if (already_set($1->key, &@2, state)) { +- expr_free($3); +- YYERROR; +- } +- $1->key = $3; +- datatype_set($1->key, $3->dtype); ++ $1->key = $2; + $$ = $1; + } + | set_block FLAGS set_flag_list stmt_separator diff --git a/0078-rule-fix-ASAN-errors-in-chain-priority-to-textual-na.patch b/0078-rule-fix-ASAN-errors-in-chain-priority-to-textual-na.patch new file mode 100644 index 0000000..d0cbe4d --- /dev/null +++ b/0078-rule-fix-ASAN-errors-in-chain-priority-to-textual-na.patch @@ -0,0 +1,82 @@ +From cbd3c93551dab7359fa72f4a8140032f72a00262 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:42 +0200 +Subject: [PATCH] rule: fix ASAN errors in chain priority to textual names + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit ff6135270616ccf4712990246cae850e64253516 + +commit ff6135270616ccf4712990246cae850e64253516 +Author: Pablo Neira Ayuso +Date: Thu Feb 29 16:50:37 2024 +0100 + + rule: fix ASAN errors in chain priority to textual names + + ASAN reports several errors when listing this ruleset: + + table ip x { + chain y { + type filter hook input priority -2147483648; policy accept; + } + } + + src/rule.c:1002:8: runtime error: negation of -2147483648 cannot be represented in type 'int'; cast to an unsigned type to negate this value to itself + src/rule.c:1001:11: runtime error: signed integer overflow: -2147483648 - 50 cannot be represented in type 'int' + + Use int64_t for the offset to avoid an underflow when calculating + closest existing priority definition. + + Use llabs() because abs() is undefined with INT32_MIN. + + Fixes: c8a0e8c90e2d ("src: Set/print standard chain prios with textual names") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/rule.c | 15 +++++++++------ + 1 file changed, 9 insertions(+), 6 deletions(-) + +diff --git a/src/rule.c b/src/rule.c +index e2d972c..85718c7 100644 +--- a/src/rule.c ++++ b/src/rule.c +@@ -977,10 +977,11 @@ static const char *prio2str(const struct output_ctx *octx, + const struct expr *expr) + { + const struct prio_tag *prio_arr; +- int std_prio, offset, prio; ++ const uint32_t reach = 10; + const char *std_prio_str; +- const int reach = 10; ++ int std_prio, prio; + size_t i, arr_size; ++ int64_t offset; + + mpz_export_data(&prio, expr->value, BYTEORDER_HOST_ENDIAN, sizeof(int)); + if (family == NFPROTO_BRIDGE) { +@@ -995,19 +996,21 @@ static const char *prio2str(const struct output_ctx *octx, + for (i = 0; i < arr_size; ++i) { + std_prio = prio_arr[i].val; + std_prio_str = prio_arr[i].str; +- if (abs(prio - std_prio) <= reach) { ++ ++ offset = (int64_t)prio - std_prio; ++ if (llabs(offset) <= reach) { + if (!std_prio_family_hook_compat(std_prio, + family, hook)) + break; +- offset = prio - std_prio; ++ + strncpy(buf, std_prio_str, bufsize); + if (offset > 0) + snprintf(buf + strlen(buf), +- bufsize - strlen(buf), " + %d", ++ bufsize - strlen(buf), " + %" PRIu64, + offset); + else if (offset < 0) + snprintf(buf + strlen(buf), +- bufsize - strlen(buf), " - %d", ++ bufsize - strlen(buf), " - %" PRIu64, + -offset); + return buf; + } diff --git a/0079-nftables-do-mot-merge-payloads-on-negation.patch b/0079-nftables-do-mot-merge-payloads-on-negation.patch new file mode 100644 index 0000000..556f990 --- /dev/null +++ b/0079-nftables-do-mot-merge-payloads-on-negation.patch @@ -0,0 +1,40 @@ +From b146a3efdff7e834790e3ee8b605e5e7033fb8c9 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:42 +0200 +Subject: [PATCH] nftables: do mot merge payloads on negation + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit f35a0d78fe870737fa39d859bd2e3ac25bf1b12e + +commit f35a0d78fe870737fa39d859bd2e3ac25bf1b12e +Author: Sriram Rajagopalan +Date: Wed Mar 13 01:32:42 2024 -0700 + + nftables: do mot merge payloads on negation + + else, a rule like + tcp sport != 22 tcp dport != 23 + + will match even if the destination is 23 as long as sport is != 22. + (or vice versa). + + Signed-off-by: Sriram Rajagopalan + Signed-off-by: Florian Westphal + +Signed-off-by: Phil Sutter +--- + src/rule.c | 1 - + 1 file changed, 1 deletion(-) + +diff --git a/src/rule.c b/src/rule.c +index 85718c7..89101f9 100644 +--- a/src/rule.c ++++ b/src/rule.c +@@ -2778,7 +2778,6 @@ static void stmt_reduce(const struct rule *rule) + switch (stmt->expr->op) { + case OP_EQ: + case OP_IMPLICIT: +- case OP_NEQ: + break; + default: + continue; diff --git a/0080-tests-py-add-payload-merging-test-cases.patch b/0080-tests-py-add-payload-merging-test-cases.patch new file mode 100644 index 0000000..baf232c --- /dev/null +++ b/0080-tests-py-add-payload-merging-test-cases.patch @@ -0,0 +1,337 @@ +From a2ec860212bf27ca45a5c9ec55b91b3eb1c8060f Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:42 +0200 +Subject: [PATCH] tests: py: add payload merging test cases + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 04fc306359a49bc9f314ae82520218f5dfebfef1 + +commit 04fc306359a49bc9f314ae82520218f5dfebfef1 +Author: Florian Westphal +Date: Fri Mar 8 14:40:12 2024 +0100 + + tests: py: add payload merging test cases + + Add a test case that would fail without preceeding fix. + + Signed-off-by: Florian Westphal + +Signed-off-by: Phil Sutter +--- + tests/py/inet/payloadmerge.t | 14 ++ + tests/py/inet/payloadmerge.t.json | 211 +++++++++++++++++++++++++++ + tests/py/inet/payloadmerge.t.payload | 66 +++++++++ + 3 files changed, 291 insertions(+) + create mode 100644 tests/py/inet/payloadmerge.t + create mode 100644 tests/py/inet/payloadmerge.t.json + create mode 100644 tests/py/inet/payloadmerge.t.payload + +diff --git a/tests/py/inet/payloadmerge.t b/tests/py/inet/payloadmerge.t +new file mode 100644 +index 0000000..04ba1ce +--- /dev/null ++++ b/tests/py/inet/payloadmerge.t +@@ -0,0 +1,14 @@ ++:input;type filter hook input priority 0 ++ ++*ip;test-ip4;input ++*ip6;test-ip6;input ++*inet;test-inet;input ++ ++tcp sport 1 tcp dport 2;ok ++tcp sport != 1 tcp dport != 2;ok ++tcp sport 1 tcp dport != 2;ok ++tcp sport != 1 tcp dport 2;ok ++meta l4proto != 6 th dport 2;ok ++meta l4proto 6 tcp dport 22;ok;tcp dport 22 ++tcp sport > 1 tcp dport > 2;ok ++tcp sport 1 tcp dport > 2;ok +diff --git a/tests/py/inet/payloadmerge.t.json b/tests/py/inet/payloadmerge.t.json +new file mode 100644 +index 0000000..e5b66cf +--- /dev/null ++++ b/tests/py/inet/payloadmerge.t.json +@@ -0,0 +1,211 @@ ++# tcp sport 1 tcp dport 2 ++[ ++ { ++ "match": { ++ "left": { ++ "payload": { ++ "field": "sport", ++ "protocol": "tcp" ++ } ++ }, ++ "op": "==", ++ "right": 1 ++ } ++ }, ++ { ++ "match": { ++ "left": { ++ "payload": { ++ "field": "dport", ++ "protocol": "tcp" ++ } ++ }, ++ "op": "==", ++ "right": 2 ++ } ++ } ++] ++ ++# tcp sport != 1 tcp dport != 2 ++[ ++ { ++ "match": { ++ "left": { ++ "payload": { ++ "field": "sport", ++ "protocol": "tcp" ++ } ++ }, ++ "op": "!=", ++ "right": 1 ++ } ++ }, ++ { ++ "match": { ++ "left": { ++ "payload": { ++ "field": "dport", ++ "protocol": "tcp" ++ } ++ }, ++ "op": "!=", ++ "right": 2 ++ } ++ } ++] ++ ++# tcp sport 1 tcp dport != 2 ++[ ++ { ++ "match": { ++ "left": { ++ "payload": { ++ "field": "sport", ++ "protocol": "tcp" ++ } ++ }, ++ "op": "==", ++ "right": 1 ++ } ++ }, ++ { ++ "match": { ++ "left": { ++ "payload": { ++ "field": "dport", ++ "protocol": "tcp" ++ } ++ }, ++ "op": "!=", ++ "right": 2 ++ } ++ } ++] ++ ++# tcp sport != 1 tcp dport 2 ++[ ++ { ++ "match": { ++ "left": { ++ "payload": { ++ "field": "sport", ++ "protocol": "tcp" ++ } ++ }, ++ "op": "!=", ++ "right": 1 ++ } ++ }, ++ { ++ "match": { ++ "left": { ++ "payload": { ++ "field": "dport", ++ "protocol": "tcp" ++ } ++ }, ++ "op": "==", ++ "right": 2 ++ } ++ } ++] ++ ++# meta l4proto != 6 th dport 2 ++[ ++ { ++ "match": { ++ "left": { ++ "meta": { ++ "key": "l4proto" ++ } ++ }, ++ "op": "!=", ++ "right": 6 ++ } ++ }, ++ { ++ "match": { ++ "left": { ++ "payload": { ++ "field": "dport", ++ "protocol": "th" ++ } ++ }, ++ "op": "==", ++ "right": 2 ++ } ++ } ++] ++ ++# meta l4proto 6 tcp dport 22 ++[ ++ { ++ "match": { ++ "left": { ++ "payload": { ++ "field": "dport", ++ "protocol": "tcp" ++ } ++ }, ++ "op": "==", ++ "right": 22 ++ } ++ } ++] ++ ++# tcp sport > 1 tcp dport > 2 ++[ ++ { ++ "match": { ++ "left": { ++ "payload": { ++ "field": "sport", ++ "protocol": "tcp" ++ } ++ }, ++ "op": ">", ++ "right": 1 ++ } ++ }, ++ { ++ "match": { ++ "left": { ++ "payload": { ++ "field": "dport", ++ "protocol": "tcp" ++ } ++ }, ++ "op": ">", ++ "right": 2 ++ } ++ } ++] ++ ++# tcp sport 1 tcp dport > 2 ++[ ++ { ++ "match": { ++ "left": { ++ "payload": { ++ "field": "sport", ++ "protocol": "tcp" ++ } ++ }, ++ "op": "==", ++ "right": 1 ++ } ++ }, ++ { ++ "match": { ++ "left": { ++ "payload": { ++ "field": "dport", ++ "protocol": "tcp" ++ } ++ }, ++ "op": ">", ++ "right": 2 ++ } ++ } ++] ++ +diff --git a/tests/py/inet/payloadmerge.t.payload b/tests/py/inet/payloadmerge.t.payload +new file mode 100644 +index 0000000..a0465cd +--- /dev/null ++++ b/tests/py/inet/payloadmerge.t.payload +@@ -0,0 +1,66 @@ ++# tcp sport 1 tcp dport 2 ++ip test-ip4 input ++ [ meta load l4proto => reg 1 ] ++ [ cmp eq reg 1 0x00000006 ] ++ [ payload load 4b @ transport header + 0 => reg 1 ] ++ [ cmp eq reg 1 0x02000100 ] ++ ++# tcp sport != 1 tcp dport != 2 ++ip test-ip4 input ++ [ meta load l4proto => reg 1 ] ++ [ cmp eq reg 1 0x00000006 ] ++ [ payload load 2b @ transport header + 0 => reg 1 ] ++ [ cmp neq reg 1 0x00000100 ] ++ [ payload load 2b @ transport header + 2 => reg 1 ] ++ [ cmp neq reg 1 0x00000200 ] ++ ++# tcp sport 1 tcp dport != 2 ++ip test-ip4 input ++ [ meta load l4proto => reg 1 ] ++ [ cmp eq reg 1 0x00000006 ] ++ [ payload load 2b @ transport header + 0 => reg 1 ] ++ [ cmp eq reg 1 0x00000100 ] ++ [ payload load 2b @ transport header + 2 => reg 1 ] ++ [ cmp neq reg 1 0x00000200 ] ++ ++# tcp sport != 1 tcp dport 2 ++ip test-ip4 input ++ [ meta load l4proto => reg 1 ] ++ [ cmp eq reg 1 0x00000006 ] ++ [ payload load 2b @ transport header + 0 => reg 1 ] ++ [ cmp neq reg 1 0x00000100 ] ++ [ payload load 2b @ transport header + 2 => reg 1 ] ++ [ cmp eq reg 1 0x00000200 ] ++ ++# meta l4proto != 6 th dport 2 ++ip test-ip4 input ++ [ meta load l4proto => reg 1 ] ++ [ cmp neq reg 1 0x00000006 ] ++ [ payload load 2b @ transport header + 2 => reg 1 ] ++ [ cmp eq reg 1 0x00000200 ] ++ ++# meta l4proto 6 tcp dport 22 ++ip test-ip4 input ++ [ meta load l4proto => reg 1 ] ++ [ cmp eq reg 1 0x00000006 ] ++ [ payload load 2b @ transport header + 2 => reg 1 ] ++ [ cmp eq reg 1 0x00001600 ] ++ ++# tcp sport > 1 tcp dport > 2 ++ip test-ip4 input ++ [ meta load l4proto => reg 1 ] ++ [ cmp eq reg 1 0x00000006 ] ++ [ payload load 2b @ transport header + 0 => reg 1 ] ++ [ cmp gt reg 1 0x00000100 ] ++ [ payload load 2b @ transport header + 2 => reg 1 ] ++ [ cmp gt reg 1 0x00000200 ] ++ ++# tcp sport 1 tcp dport > 2 ++ip test-ip4 input ++ [ meta load l4proto => reg 1 ] ++ [ cmp eq reg 1 0x00000006 ] ++ [ payload load 2b @ transport header + 0 => reg 1 ] ++ [ cmp eq reg 1 0x00000100 ] ++ [ payload load 2b @ transport header + 2 => reg 1 ] ++ [ cmp gt reg 1 0x00000200 ] ++ diff --git a/0081-parser-json-Support-for-synproxy-objects.patch b/0081-parser-json-Support-for-synproxy-objects.patch new file mode 100644 index 0000000..aba0e9a --- /dev/null +++ b/0081-parser-json-Support-for-synproxy-objects.patch @@ -0,0 +1,65 @@ +From 215992d2a9c837410eb84bad86307d303d4cd103 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:42 +0200 +Subject: [PATCH] parser: json: Support for synproxy objects + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 938a135f2200766661e42a20e02d87555f5bacfa + +commit 938a135f2200766661e42a20e02d87555f5bacfa +Author: Phil Sutter +Date: Sat Mar 9 00:29:36 2024 +0100 + + parser: json: Support for synproxy objects + + Parsing code was there already, merely the entry in json_parse_cmd_add() + missing. + + To support maps with synproxy target, an entry in string_to_nft_object() + is required. While being at it, add other missing entries as well. + + Signed-off-by: Phil Sutter + +Signed-off-by: Phil Sutter +--- + src/parser_json.c | 17 +++++++++++------ + 1 file changed, 11 insertions(+), 6 deletions(-) + +diff --git a/src/parser_json.c b/src/parser_json.c +index 9537c9d..b8ed848 100644 +--- a/src/parser_json.c ++++ b/src/parser_json.c +@@ -3255,14 +3255,18 @@ err_free_rule: + static int string_to_nft_object(const char *str) + { + const char *obj_tbl[__NFT_OBJECT_MAX] = { +- [NFT_OBJECT_COUNTER] = "counter", +- [NFT_OBJECT_QUOTA] = "quota", +- [NFT_OBJECT_LIMIT] = "limit", +- [NFT_OBJECT_SECMARK] = "secmark", ++ [NFT_OBJECT_COUNTER] = "counter", ++ [NFT_OBJECT_QUOTA] = "quota", ++ [NFT_OBJECT_CT_HELPER] = "ct helper", ++ [NFT_OBJECT_LIMIT] = "limit", ++ [NFT_OBJECT_CT_TIMEOUT] = "ct timeout", ++ [NFT_OBJECT_SECMARK] = "secmark", ++ [NFT_OBJECT_CT_EXPECT] = "ct expectation", ++ [NFT_OBJECT_SYNPROXY] = "synproxy", + }; + unsigned int i; + +- for (i = 0; i < NFT_OBJECT_MAX; i++) { ++ for (i = 0; i <= NFT_OBJECT_MAX; i++) { + if (obj_tbl[i] && !strcmp(str, obj_tbl[i])) + return i; + } +@@ -3789,7 +3793,8 @@ static struct cmd *json_parse_cmd_add(struct json_ctx *ctx, + { "ct timeout", NFT_OBJECT_CT_TIMEOUT, json_parse_cmd_add_object }, + { "ct expectation", NFT_OBJECT_CT_EXPECT, json_parse_cmd_add_object }, + { "limit", CMD_OBJ_LIMIT, json_parse_cmd_add_object }, +- { "secmark", CMD_OBJ_SECMARK, json_parse_cmd_add_object } ++ { "secmark", CMD_OBJ_SECMARK, json_parse_cmd_add_object }, ++ { "synproxy", CMD_OBJ_SYNPROXY, json_parse_cmd_add_object } + }; + unsigned int i; + json_t *tmp; diff --git a/0082-doc-libnftables-json-Drop-invalid-ops-from-match-exp.patch b/0082-doc-libnftables-json-Drop-invalid-ops-from-match-exp.patch new file mode 100644 index 0000000..eff282c --- /dev/null +++ b/0082-doc-libnftables-json-Drop-invalid-ops-from-match-exp.patch @@ -0,0 +1,40 @@ +From 86c010da124c480215b3a69fd08d3081b08862b1 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:42 +0200 +Subject: [PATCH] doc: libnftables-json: Drop invalid ops from match expression + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 840cb6df16973139a1981fcec276f59d3b92bb46 + +commit 840cb6df16973139a1981fcec276f59d3b92bb46 +Author: Phil Sutter +Date: Fri Sep 22 18:43:11 2023 +0200 + + doc: libnftables-json: Drop invalid ops from match expression + + These make no sense there and are listed again in BINARY OPERATION. + + Fixes: 872f373dc50f7 ("doc: Add JSON schema documentation") + Signed-off-by: Phil Sutter + +Signed-off-by: Phil Sutter +--- + doc/libnftables-json.adoc | 5 ----- + 1 file changed, 5 deletions(-) + +diff --git a/doc/libnftables-json.adoc b/doc/libnftables-json.adoc +index 3bbb6cd..cc70f0f 100644 +--- a/doc/libnftables-json.adoc ++++ b/doc/libnftables-json.adoc +@@ -695,11 +695,6 @@ processing continues with the next rule in the same chain. + ==== OPERATORS + + [horizontal] +-*&*:: Binary AND +-*|*:: Binary OR +-*^*:: Binary XOR +-*<<*:: Left shift +-*>>*:: Right shift + *==*:: Equal + *!=*:: Not equal + *<*:: Less than diff --git a/0083-netlink_delinearize-restore-binop-syntax-when-listin.patch b/0083-netlink_delinearize-restore-binop-syntax-when-listin.patch new file mode 100644 index 0000000..3da49b4 --- /dev/null +++ b/0083-netlink_delinearize-restore-binop-syntax-when-listin.patch @@ -0,0 +1,604 @@ +From 86c0cc20f38f8503ea670f8be664278c24f9edcf Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:42 +0200 +Subject: [PATCH] netlink_delinearize: restore binop syntax when listing + ruleset for flags + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit b11b6c68e61ea294eb4c313705ccfe3e7b0eda87 + +commit b11b6c68e61ea294eb4c313705ccfe3e7b0eda87 +Author: Pablo Neira Ayuso +Date: Mon Mar 18 13:10:55 2024 +0100 + + netlink_delinearize: restore binop syntax when listing ruleset for flags + + c3d57114f119 ("parser_bison: add shortcut syntax for matching flags + without binary operations") provides a similar syntax to iptables using + a prefix representation for flag matching. + + Restore original representation using binop when listing the ruleset. + The parser still accepts the prefix notation for backward compatibility. + + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/netlink_delinearize.c | 65 ++-- + tests/py/inet/tcp.t | 16 +- + tests/py/inet/tcp.t.json | 42 ++- + tests/py/inet/tcp.t.json.output | 279 ++++++++++++++++++ + tests/py/inet/tcp.t.payload | 6 +- + .../testcases/nft-f/dumps/sample-ruleset.nft | 4 +- + tests/shell/testcases/packetpath/tcp_options | 16 +- + 7 files changed, 346 insertions(+), 82 deletions(-) + +diff --git a/src/netlink_delinearize.c b/src/netlink_delinearize.c +index 1d30a78..405a065 100644 +--- a/src/netlink_delinearize.c ++++ b/src/netlink_delinearize.c +@@ -2517,56 +2517,29 @@ static void relational_binop_postprocess(struct rule_pp_ctx *ctx, + + if (binop->op == OP_AND && (expr->op == OP_NEQ || expr->op == OP_EQ) && + right->dtype->basetype && +- right->dtype->basetype->type == TYPE_BITMASK) { +- switch (right->etype) { +- case EXPR_VALUE: +- if (!mpz_cmp_ui(right->value, 0)) { +- /* Flag comparison: data & flags != 0 +- * +- * Split the flags into a list of flag values and convert the +- * op to OP_EQ. +- */ +- expr_free(right); +- +- expr->left = expr_get(binop->left); +- expr->right = binop_tree_to_list(NULL, binop->right); +- switch (expr->op) { +- case OP_NEQ: +- expr->op = OP_IMPLICIT; +- break; +- case OP_EQ: +- expr->op = OP_NEG; +- break; +- default: +- BUG("unknown operation type %d\n", expr->op); +- } +- expr_free(binop); +- } else if (binop->right->etype == EXPR_VALUE && +- right->etype == EXPR_VALUE && +- !mpz_cmp(right->value, binop->right->value)) { +- /* Skip flag / flag representation for: +- * data & flag == flag +- * data & flag != flag +- */ +- ; +- } else { +- *exprp = flagcmp_expr_alloc(&expr->location, expr->op, +- expr_get(binop->left), +- binop_tree_to_list(NULL, binop->right), +- expr_get(right)); +- expr_free(expr); +- } ++ right->dtype->basetype->type == TYPE_BITMASK && ++ right->etype == EXPR_VALUE && ++ !mpz_cmp_ui(right->value, 0)) { ++ /* Flag comparison: data & flags != 0 ++ * ++ * Split the flags into a list of flag values and convert the ++ * op to OP_EQ. ++ */ ++ expr_free(right); ++ ++ expr->left = expr_get(binop->left); ++ expr->right = binop_tree_to_list(NULL, binop->right); ++ switch (expr->op) { ++ case OP_NEQ: ++ expr->op = OP_IMPLICIT; + break; +- case EXPR_BINOP: +- *exprp = flagcmp_expr_alloc(&expr->location, expr->op, +- expr_get(binop->left), +- binop_tree_to_list(NULL, binop->right), +- binop_tree_to_list(NULL, right)); +- expr_free(expr); ++ case OP_EQ: ++ expr->op = OP_NEG; + break; + default: +- break; ++ BUG("unknown operation type %d\n", expr->op); + } ++ expr_free(binop); + } else if (binop->left->dtype->flags & DTYPE_F_PREFIX && + binop->op == OP_AND && expr->right->etype == EXPR_VALUE && + expr_mask_is_prefix(binop->right)) { +diff --git a/tests/py/inet/tcp.t b/tests/py/inet/tcp.t +index f51ebd3..f4bdac1 100644 +--- a/tests/py/inet/tcp.t ++++ b/tests/py/inet/tcp.t +@@ -68,8 +68,8 @@ tcp flags != { fin, urg, ecn, cwr} drop;ok + tcp flags cwr;ok + tcp flags != cwr;ok + tcp flags == syn;ok +-tcp flags fin,syn / fin,syn;ok +-tcp flags != syn / fin,syn;ok ++tcp flags fin,syn / fin,syn;ok;tcp flags & (fin | syn) == fin | syn ++tcp flags != syn / fin,syn;ok;tcp flags & (fin | syn) != syn + tcp flags & syn != 0;ok;tcp flags syn + tcp flags & syn == 0;ok;tcp flags ! syn + tcp flags & (syn | ack) != 0;ok;tcp flags syn,ack +@@ -77,12 +77,12 @@ tcp flags & (syn | ack) == 0;ok;tcp flags ! syn,ack + # it should be possible to transform this to: tcp flags syn + tcp flags & syn == syn;ok + tcp flags & syn != syn;ok +-tcp flags & (fin | syn | rst | ack) syn;ok;tcp flags syn / fin,syn,rst,ack +-tcp flags & (fin | syn | rst | ack) == syn;ok;tcp flags syn / fin,syn,rst,ack +-tcp flags & (fin | syn | rst | ack) != syn;ok;tcp flags != syn / fin,syn,rst,ack +-tcp flags & (fin | syn | rst | ack) == (syn | ack);ok;tcp flags syn,ack / fin,syn,rst,ack +-tcp flags & (fin | syn | rst | ack) != (syn | ack);ok;tcp flags != syn,ack / fin,syn,rst,ack +-tcp flags & (syn | ack) == (syn | ack);ok;tcp flags syn,ack / syn,ack ++tcp flags & (fin | syn | rst | ack) syn;ok;tcp flags & (fin | syn | rst | ack) == syn ++tcp flags & (fin | syn | rst | ack) == syn;ok ++tcp flags & (fin | syn | rst | ack) != syn;ok ++tcp flags & (fin | syn | rst | ack) == syn | ack;ok ++tcp flags & (fin | syn | rst | ack) != syn | ack;ok ++tcp flags & (syn | ack) == syn | ack;ok + tcp flags & (fin | syn | rst | psh | ack | urg | ecn | cwr) == fin | syn | rst | psh | ack | urg | ecn | cwr;ok;tcp flags == 0xff + tcp flags { syn, syn | ack };ok + tcp flags & (fin | syn | rst | psh | ack | urg) == { fin, ack, psh | ack, fin | psh | ack };ok +diff --git a/tests/py/inet/tcp.t.json b/tests/py/inet/tcp.t.json +index 8439c2b..d3a846c 100644 +--- a/tests/py/inet/tcp.t.json ++++ b/tests/py/inet/tcp.t.json +@@ -1712,7 +1712,7 @@ + } + ] + +-# tcp flags & (fin | syn | rst | ack) == (syn | ack) ++# tcp flags & (fin | syn | rst | ack) == syn | ack + [ + { + "match": { +@@ -1741,7 +1741,7 @@ + } + ] + +-# tcp flags & (fin | syn | rst | ack) != (syn | ack) ++# tcp flags & (syn | ack) == syn | ack + [ + { + "match": { +@@ -1754,14 +1754,12 @@ + } + }, + [ +- "fin", + "syn", +- "rst", + "ack" + ] + ] + }, +- "op": "!=", ++ "op": "==", + "right": [ + "syn", + "ack" +@@ -1770,7 +1768,7 @@ + } + ] + +-# tcp flags & (syn | ack) == (syn | ack) ++# tcp flags & (fin | syn | rst | ack) != syn | ack + [ + { + "match": { +@@ -1782,17 +1780,31 @@ + "protocol": "tcp" + } + }, +- [ +- "syn", +- "ack" +- ] ++ { ++ "|": [ ++ { ++ "|": [ ++ { ++ "|": [ ++ "fin", ++ "syn" ++ ] ++ }, ++ "rst" ++ ] ++ }, ++ "ack" ++ ] ++ } + ] + }, +- "op": "==", +- "right": [ +- "syn", +- "ack" +- ] ++ "op": "!=", ++ "right": { ++ "|": [ ++ "syn", ++ "ack" ++ ] ++ } + } + } + ] +diff --git a/tests/py/inet/tcp.t.json.output b/tests/py/inet/tcp.t.json.output +index c471e8d..e186e12 100644 +--- a/tests/py/inet/tcp.t.json.output ++++ b/tests/py/inet/tcp.t.json.output +@@ -208,3 +208,282 @@ + } + } + ] ++ ++# tcp flags fin,syn / fin,syn ++[ ++ { ++ "match": { ++ "left": { ++ "&": [ ++ { ++ "payload": { ++ "field": "flags", ++ "protocol": "tcp" ++ } ++ }, ++ { ++ "|": [ ++ "fin", ++ "syn" ++ ] ++ } ++ ] ++ }, ++ "op": "==", ++ "right": { ++ "|": [ ++ "fin", ++ "syn" ++ ] ++ } ++ } ++ } ++] ++ ++# tcp flags != syn / fin,syn ++[ ++ { ++ "match": { ++ "left": { ++ "&": [ ++ { ++ "payload": { ++ "field": "flags", ++ "protocol": "tcp" ++ } ++ }, ++ { ++ "|": [ ++ "fin", ++ "syn" ++ ] ++ } ++ ] ++ }, ++ "op": "!=", ++ "right": "syn" ++ } ++ } ++] ++ ++# tcp flags & (fin | syn | rst | ack) syn ++[ ++ { ++ "match": { ++ "left": { ++ "&": [ ++ { ++ "payload": { ++ "field": "flags", ++ "protocol": "tcp" ++ } ++ }, ++ { ++ "|": [ ++ { ++ "|": [ ++ { ++ "|": [ ++ "fin", ++ "syn" ++ ] ++ }, ++ "rst" ++ ] ++ }, ++ "ack" ++ ] ++ } ++ ] ++ }, ++ "op": "==", ++ "right": "syn" ++ } ++ } ++] ++ ++# tcp flags & (fin | syn | rst | ack) == syn ++[ ++ { ++ "match": { ++ "left": { ++ "&": [ ++ { ++ "payload": { ++ "field": "flags", ++ "protocol": "tcp" ++ } ++ }, ++ { ++ "|": [ ++ { ++ "|": [ ++ { ++ "|": [ ++ "fin", ++ "syn" ++ ] ++ }, ++ "rst" ++ ] ++ }, ++ "ack" ++ ] ++ } ++ ] ++ }, ++ "op": "==", ++ "right": "syn" ++ } ++ } ++] ++ ++# tcp flags & (fin | syn | rst | ack) != syn ++[ ++ { ++ "match": { ++ "left": { ++ "&": [ ++ { ++ "payload": { ++ "field": "flags", ++ "protocol": "tcp" ++ } ++ }, ++ { ++ "|": [ ++ { ++ "|": [ ++ { ++ "|": [ ++ "fin", ++ "syn" ++ ] ++ }, ++ "rst" ++ ] ++ }, ++ "ack" ++ ] ++ } ++ ] ++ }, ++ "op": "!=", ++ "right": "syn" ++ } ++ } ++] ++ ++# tcp flags & (fin | syn | rst | ack) == syn | ack ++[ ++ { ++ "match": { ++ "left": { ++ "&": [ ++ { ++ "payload": { ++ "field": "flags", ++ "protocol": "tcp" ++ } ++ }, ++ { ++ "|": [ ++ { ++ "|": [ ++ { ++ "|": [ ++ "fin", ++ "syn" ++ ] ++ }, ++ "rst" ++ ] ++ }, ++ "ack" ++ ] ++ } ++ ] ++ }, ++ "op": "==", ++ "right": { ++ "|": [ ++ "syn", ++ "ack" ++ ] ++ } ++ } ++ } ++] ++ ++# tcp flags & (fin | syn | rst | ack) != syn | ack ++[ ++ { ++ "match": { ++ "left": { ++ "&": [ ++ { ++ "payload": { ++ "field": "flags", ++ "protocol": "tcp" ++ } ++ }, ++ { ++ "|": [ ++ { ++ "|": [ ++ { ++ "|": [ ++ "fin", ++ "syn" ++ ] ++ }, ++ "rst" ++ ] ++ }, ++ "ack" ++ ] ++ } ++ ] ++ }, ++ "op": "!=", ++ "right": { ++ "|": [ ++ "syn", ++ "ack" ++ ] ++ } ++ } ++ } ++] ++ ++# tcp flags & (syn | ack) == syn | ack ++[ ++ { ++ "match": { ++ "left": { ++ "&": [ ++ { ++ "payload": { ++ "field": "flags", ++ "protocol": "tcp" ++ } ++ }, ++ { ++ "|": [ ++ "syn", ++ "ack" ++ ] ++ } ++ ] ++ }, ++ "op": "==", ++ "right": { ++ "|": [ ++ "syn", ++ "ack" ++ ] ++ } ++ } ++ } ++] ++ +diff --git a/tests/py/inet/tcp.t.payload b/tests/py/inet/tcp.t.payload +index 1cfe500..bc6bb98 100644 +--- a/tests/py/inet/tcp.t.payload ++++ b/tests/py/inet/tcp.t.payload +@@ -442,7 +442,7 @@ inet test-inet input + [ bitwise reg 1 = ( reg 1 & 0x00000017 ) ^ 0x00000000 ] + [ cmp neq reg 1 0x00000002 ] + +-# tcp flags & (fin | syn | rst | ack) == (syn | ack) ++# tcp flags & (fin | syn | rst | ack) == syn | ack + inet test-inet input + [ meta load l4proto => reg 1 ] + [ cmp eq reg 1 0x00000006 ] +@@ -450,7 +450,7 @@ inet test-inet input + [ bitwise reg 1 = ( reg 1 & 0x00000017 ) ^ 0x00000000 ] + [ cmp eq reg 1 0x00000012 ] + +-# tcp flags & (fin | syn | rst | ack) != (syn | ack) ++# tcp flags & (fin | syn | rst | ack) != syn | ack + inet test-inet input + [ meta load l4proto => reg 1 ] + [ cmp eq reg 1 0x00000006 ] +@@ -458,7 +458,7 @@ inet test-inet input + [ bitwise reg 1 = ( reg 1 & 0x00000017 ) ^ 0x00000000 ] + [ cmp neq reg 1 0x00000012 ] + +-# tcp flags & (syn | ack) == (syn | ack) ++# tcp flags & (syn | ack) == syn | ack + inet test-inet input + [ meta load l4proto => reg 1 ] + [ cmp eq reg 1 0x00000006 ] +diff --git a/tests/shell/testcases/nft-f/dumps/sample-ruleset.nft b/tests/shell/testcases/nft-f/dumps/sample-ruleset.nft +index 480b694..1a9f4e7 100644 +--- a/tests/shell/testcases/nft-f/dumps/sample-ruleset.nft ++++ b/tests/shell/testcases/nft-f/dumps/sample-ruleset.nft +@@ -73,7 +73,7 @@ table inet filter { + + chain ct_new_pre { + jump rpfilter +- tcp flags != syn / fin,syn,rst,ack counter packets 0 bytes 0 drop ++ tcp flags & (fin | syn | rst | ack) != syn counter packets 0 bytes 0 drop + iifname "eth0" meta nfproto vmap { ipv4 : jump blacklist_input_ipv4, ipv6 : jump blacklist_input_ipv6 } + } + +@@ -131,7 +131,7 @@ table inet filter { + type filter hook forward priority mangle; policy accept; + oifname "eth0" jump { + ct state new meta nfproto vmap { ipv4 : jump blacklist_output_ipv4, ipv6 : jump blacklist_output_ipv6 } +- tcp flags syn / syn,rst tcp option maxseg size set rt mtu ++ tcp flags & (syn | rst) == syn tcp option maxseg size set rt mtu + } + } + +diff --git a/tests/shell/testcases/packetpath/tcp_options b/tests/shell/testcases/packetpath/tcp_options +index 1c9ee53..8855222 100755 +--- a/tests/shell/testcases/packetpath/tcp_options ++++ b/tests/shell/testcases/packetpath/tcp_options +@@ -15,14 +15,14 @@ table inet t { + chain c { + type filter hook output priority 0; + tcp dport != 22345 accept +- tcp flags syn / fin,syn,rst,ack tcp option 254 length ge 4 counter name nomatchc drop +- tcp flags syn / fin,syn,rst,ack tcp option fastopen length ge 2 reset tcp option fastopen counter name nomatchc +- tcp flags syn / fin,syn,rst,ack tcp option sack-perm missing counter name nomatchc +- tcp flags syn / fin,syn,rst,ack tcp option sack-perm exists counter name sackpermc +- tcp flags syn / fin,syn,rst,ack tcp option maxseg size gt 1400 counter name maxsegc +- tcp flags syn / fin,syn,rst,ack tcp option nop missing counter name nomatchc +- tcp flags syn / fin,syn,rst,ack tcp option nop exists counter name nopc +- tcp flags syn / fin,syn,rst,ack drop ++ tcp flags & (fin | syn | rst | ack ) == syn tcp option 254 length ge 4 counter name nomatchc drop ++ tcp flags & (fin | syn | rst | ack ) == syn tcp option fastopen length ge 2 reset tcp option fastopen counter name nomatchc ++ tcp flags & (fin | syn | rst | ack ) == syn tcp option sack-perm missing counter name nomatchc ++ tcp flags & (fin | syn | rst | ack) == syn tcp option sack-perm exists counter name sackpermc ++ tcp flags & (fin | syn | rst | ack) == syn tcp option maxseg size gt 1400 counter name maxsegc ++ tcp flags & (fin | syn | rst | ack) == syn tcp option nop missing counter name nomatchc ++ tcp flags & (fin | syn | rst | ack) == syn tcp option nop exists counter name nopc ++ tcp flags & (fin | syn | rst | ack) == syn drop + } + } + EOF diff --git a/0084-evaluate-display-Range-negative-size-error.patch b/0084-evaluate-display-Range-negative-size-error.patch new file mode 100644 index 0000000..e9be9e3 --- /dev/null +++ b/0084-evaluate-display-Range-negative-size-error.patch @@ -0,0 +1,40 @@ +From 8d0cfbc6790d4206414cf21e6cd65ad4fd275624 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:43 +0200 +Subject: [PATCH] evaluate: display "Range negative size" error + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit c0a5b8c6a6433ec1d4e41646dc42ccb8444c96be + +commit c0a5b8c6a6433ec1d4e41646dc42ccb8444c96be +Author: Pablo Neira Ayuso +Date: Tue Mar 19 19:50:00 2024 +0100 + + evaluate: display "Range negative size" error + + zero length ranges now allowed, therefore, update error message to refer + to negative ranges which are not possible. + + Fixes: 7a6e16040d65 ("evaluate: allow for zero length ranges") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/evaluate.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/src/evaluate.c b/src/evaluate.c +index 89656eb..38a80f2 100644 +--- a/src/evaluate.c ++++ b/src/evaluate.c +@@ -1255,8 +1255,8 @@ static int expr_evaluate_range(struct eval_ctx *ctx, struct expr **expr) + right = range->right; + + if (mpz_cmp(left->value, right->value) > 0) +- return expr_error(ctx->msgs, range, +- "Range has zero or negative size"); ++ return expr_error(ctx->msgs, range, "Range negative size"); ++ + datatype_set(range, left->dtype); + range->flags |= EXPR_F_CONSTANT; + return 0; diff --git a/0085-src-disentangle-ICMP-code-types.patch b/0085-src-disentangle-ICMP-code-types.patch new file mode 100644 index 0000000..b734503 --- /dev/null +++ b/0085-src-disentangle-ICMP-code-types.patch @@ -0,0 +1,564 @@ +From 094d220d2fcaae5b67789b58f54d59e7b016657e Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:43 +0200 +Subject: [PATCH] src: disentangle ICMP code types + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 5fecd2a6ef614eca7b0829e684449ee25982c233 + +commit 5fecd2a6ef614eca7b0829e684449ee25982c233 +Author: Pablo Neira Ayuso +Date: Tue Apr 2 00:28:24 2024 +0200 + + src: disentangle ICMP code types + + Currently, ICMP{v4,v6,inet} code datatypes only describe those that are + supported by the reject statement, but they can also be used for icmp + code matching. Moreover, ICMP code types go hand-to-hand with ICMP + types, that is, ICMP code symbols depend on the ICMP type. + + Thus, the output of: + + nft describe icmp_code + + look confusing because that only displays the values that are supported + by the reject statement. + + Disentangle this by adding internal datatypes for the reject statement + to handle the ICMP code symbol conversion to value as well as ruleset + listing. + + The existing icmp_code, icmpv6_code and icmpx_code remain in place. For + backward compatibility, a parser function is defined in case an existing + ruleset relies on these symbols. + + As for the manpage, move existing ICMP code tables from the DATA TYPES + section to the REJECT STATEMENT section, where this really belongs to. + But the icmp_code and icmpv6_code table stubs remain in the DATA TYPES + section because that describe that this is an 8-bit integer field. + + After this patch: + + # nft describe icmp_code + datatype icmp_code (icmp code) (basetype integer), 8 bits + # nft describe icmpv6_code + datatype icmpv6_code (icmpv6 code) (basetype integer), 8 bits + # nft describe icmpx_code + datatype icmpx_code (icmpx code) (basetype integer), 8 bits + + do not display the symbol table of the reject statement anymore. + + icmpx_code_type is not used anymore, but keep it in place for backward + compatibility reasons. + + And update tests/shell accordingly. + + Fixes: 5fdd0b6a0600 ("nft: complete reject support") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + doc/data-types.txt | 68 ----------------------------------- + doc/statements.txt | 74 ++++++++++++++++++++++++++++++--------- + include/datatype.h | 5 +++ + src/datatype.c | 71 +++++++++++++++++++++++++++++++++---- + src/netlink_delinearize.c | 10 +++--- + src/parser_bison.y | 12 +++---- + src/parser_json.c | 6 ++-- + tests/py/ip/icmp.t | 6 ++-- + tests/py/ip6/icmpv6.t | 8 ++--- + 9 files changed, 147 insertions(+), 113 deletions(-) + +diff --git a/doc/data-types.txt b/doc/data-types.txt +index e5ee91a..6c0e2f9 100644 +--- a/doc/data-types.txt ++++ b/doc/data-types.txt +@@ -242,28 +242,6 @@ integer + + The ICMP Code type is used to conveniently specify the ICMP header's code field. + +-.Keywords may be used when specifying the ICMP code +-[options="header"] +-|================== +-|Keyword | Value +-|net-unreachable | +-0 +-|host-unreachable | +-1 +-|prot-unreachable| +-2 +-|port-unreachable| +-3 +-|frag-needed| +-4 +-|net-prohibited| +-9 +-|host-prohibited| +-10 +-|admin-prohibited| +-13 +-|=================== +- + ICMPV6 TYPE TYPE + ~~~~~~~~~~~~~~~~ + [options="header"] +@@ -340,52 +318,6 @@ integer + + The ICMPv6 Code type is used to conveniently specify the ICMPv6 header's code field. + +-.keywords may be used when specifying the ICMPv6 code +-[options="header"] +-|================== +-|Keyword |Value +-|no-route| +-0 +-|admin-prohibited| +-1 +-|addr-unreachable| +-3 +-|port-unreachable| +-4 +-|policy-fail| +-5 +-|reject-route| +-6 +-|================== +- +-ICMPVX CODE TYPE +-~~~~~~~~~~~~~~~~ +-[options="header"] +-|================== +-|Name | Keyword | Size | Base type +-|ICMPvX Code | +-icmpx_code | +-8 bit | +-integer +-|=================== +- +-The ICMPvX Code type abstraction is a set of values which overlap between ICMP +-and ICMPv6 Code types to be used from the inet family. +- +-.keywords may be used when specifying the ICMPvX code +-[options="header"] +-|================== +-|Keyword |Value +-|no-route| +-0 +-|port-unreachable| +-1 +-|host-unreachable| +-2 +-|admin-prohibited| +-3 +-|================= +- + CONNTRACK TYPES + ~~~~~~~~~~~~~~~ + +diff --git a/doc/statements.txt b/doc/statements.txt +index c29a2ff..834adb2 100644 +--- a/doc/statements.txt ++++ b/doc/statements.txt +@@ -164,9 +164,9 @@ REJECT STATEMENT + ____ + *reject* [ *with* 'REJECT_WITH' ] + +-'REJECT_WITH' := *icmp* 'icmp_code' | +- *icmpv6* 'icmpv6_code' | +- *icmpx* 'icmpx_code' | ++'REJECT_WITH' := *icmp* 'icmp_reject_code' | ++ *icmpv6* 'icmpv6_reject_code' | ++ *icmpx* 'icmpx_reject_code' | + *tcp reset* + ____ + +@@ -177,24 +177,64 @@ using the *input*, + *forward* or *output* hooks, and user-defined chains which are only called from + those chains. + +-.different ICMP reject variants are meant for use in different table families ++.Keywords may be used to reject when specifying the ICMP code + [options="header"] + |================== +-|Variant |Family | Type +-|icmp| +-ip| +-icmp_code +-|icmpv6| +-ip6| +-icmpv6_code +-|icmpx| +-inet| +-icmpx_code ++|Keyword | Value ++|net-unreachable | ++0 ++|host-unreachable | ++1 ++|prot-unreachable| ++2 ++|port-unreachable| ++3 ++|frag-needed| ++4 ++|net-prohibited| ++9 ++|host-prohibited| ++10 ++|admin-prohibited| ++13 ++|=================== ++ ++.keywords may be used to reject when specifying the ICMPv6 code ++[options="header"] + |================== ++|Keyword |Value ++|no-route| ++0 ++|admin-prohibited| ++1 ++|addr-unreachable| ++3 ++|port-unreachable| ++4 ++|policy-fail| ++5 ++|reject-route| ++6 ++|================== ++ ++The ICMPvX Code type abstraction is a set of values which overlap between ICMP ++and ICMPv6 Code types to be used from the inet family. ++ ++.keywords may be used when specifying the ICMPvX code ++[options="header"] ++|================== ++|Keyword |Value ++|no-route| ++0 ++|port-unreachable| ++1 ++|host-unreachable| ++2 ++|admin-prohibited| ++3 ++|================= + +-For a description of the different types and a list of supported keywords refer +-to DATA TYPES section above. The common default reject value is +-*port-unreachable*. + ++The common default ICMP code to reject is *port-unreachable*. + + Note that in bridge family, reject statement is only allowed in base chains + which hook into input or prerouting. +diff --git a/include/datatype.h b/include/datatype.h +index 09a7894..68e12d5 100644 +--- a/include/datatype.h ++++ b/include/datatype.h +@@ -280,6 +280,11 @@ extern const struct datatype priority_type; + extern const struct datatype policy_type; + extern const struct datatype cgroupv2_type; + ++/* private datatypes for reject statement. */ ++extern const struct datatype reject_icmp_code_type; ++extern const struct datatype reject_icmpv6_code_type; ++extern const struct datatype reject_icmpx_code_type; ++ + void inet_service_type_print(const struct expr *expr, struct output_ctx *octx); + + extern const struct datatype *concat_type_alloc(uint32_t type); +diff --git a/src/datatype.c b/src/datatype.c +index a92f41d..46d77eb 100644 +--- a/src/datatype.c ++++ b/src/datatype.c +@@ -978,6 +978,7 @@ const struct datatype mark_type = { + .flags = DTYPE_F_PREFIX, + }; + ++/* symbol table for private datatypes for reject statement. */ + static const struct symbol_table icmp_code_tbl = { + .base = BASE_DECIMAL, + .symbols = { +@@ -993,16 +994,17 @@ static const struct symbol_table icmp_code_tbl = { + }, + }; + +-const struct datatype icmp_code_type = { +- .type = TYPE_ICMP_CODE, ++/* private datatype for reject statement. */ ++const struct datatype reject_icmp_code_type = { + .name = "icmp_code", +- .desc = "icmp code", ++ .desc = "reject icmp code", + .size = BITS_PER_BYTE, + .byteorder = BYTEORDER_BIG_ENDIAN, + .basetype = &integer_type, + .sym_tbl = &icmp_code_tbl, + }; + ++/* symbol table for private datatypes for reject statement. */ + static const struct symbol_table icmpv6_code_tbl = { + .base = BASE_DECIMAL, + .symbols = { +@@ -1016,16 +1018,17 @@ static const struct symbol_table icmpv6_code_tbl = { + }, + }; + +-const struct datatype icmpv6_code_type = { +- .type = TYPE_ICMPV6_CODE, ++/* private datatype for reject statement. */ ++const struct datatype reject_icmpv6_code_type = { + .name = "icmpv6_code", +- .desc = "icmpv6 code", ++ .desc = "reject icmpv6 code", + .size = BITS_PER_BYTE, + .byteorder = BYTEORDER_BIG_ENDIAN, + .basetype = &integer_type, + .sym_tbl = &icmpv6_code_tbl, + }; + ++/* symbol table for private datatypes for reject statement. */ + static const struct symbol_table icmpx_code_tbl = { + .base = BASE_DECIMAL, + .symbols = { +@@ -1037,6 +1040,60 @@ static const struct symbol_table icmpx_code_tbl = { + }, + }; + ++/* private datatype for reject statement. */ ++const struct datatype reject_icmpx_code_type = { ++ .name = "icmpx_code", ++ .desc = "reject icmpx code", ++ .size = BITS_PER_BYTE, ++ .byteorder = BYTEORDER_BIG_ENDIAN, ++ .basetype = &integer_type, ++ .sym_tbl = &icmpx_code_tbl, ++}; ++ ++/* Backward compatible parser for the reject statement. */ ++static struct error_record *icmp_code_parse(struct parse_ctx *ctx, ++ const struct expr *sym, ++ struct expr **res) ++{ ++ return symbolic_constant_parse(ctx, sym, &icmp_code_tbl, res); ++} ++ ++const struct datatype icmp_code_type = { ++ .type = TYPE_ICMP_CODE, ++ .name = "icmp_code", ++ .desc = "icmp code", ++ .size = BITS_PER_BYTE, ++ .byteorder = BYTEORDER_BIG_ENDIAN, ++ .basetype = &integer_type, ++ .parse = icmp_code_parse, ++}; ++ ++/* Backward compatible parser for the reject statement. */ ++static struct error_record *icmpv6_code_parse(struct parse_ctx *ctx, ++ const struct expr *sym, ++ struct expr **res) ++{ ++ return symbolic_constant_parse(ctx, sym, &icmpv6_code_tbl, res); ++} ++ ++const struct datatype icmpv6_code_type = { ++ .type = TYPE_ICMPV6_CODE, ++ .name = "icmpv6_code", ++ .desc = "icmpv6 code", ++ .size = BITS_PER_BYTE, ++ .byteorder = BYTEORDER_BIG_ENDIAN, ++ .basetype = &integer_type, ++ .parse = icmpv6_code_parse, ++}; ++ ++/* Backward compatible parser for the reject statement. */ ++static struct error_record *icmpx_code_parse(struct parse_ctx *ctx, ++ const struct expr *sym, ++ struct expr **res) ++{ ++ return symbolic_constant_parse(ctx, sym, &icmpx_code_tbl, res); ++} ++ + const struct datatype icmpx_code_type = { + .type = TYPE_ICMPX_CODE, + .name = "icmpx_code", +@@ -1044,7 +1101,7 @@ const struct datatype icmpx_code_type = { + .size = BITS_PER_BYTE, + .byteorder = BYTEORDER_BIG_ENDIAN, + .basetype = &integer_type, +- .sym_tbl = &icmpx_code_tbl, ++ .parse = icmpx_code_parse, + }; + + void time_print(uint64_t ms, struct output_ctx *octx) +diff --git a/src/netlink_delinearize.c b/src/netlink_delinearize.c +index 405a065..1af0278 100644 +--- a/src/netlink_delinearize.c ++++ b/src/netlink_delinearize.c +@@ -2930,7 +2930,7 @@ static void stmt_reject_postprocess(struct rule_pp_ctx *rctx) + switch (dl->pctx.family) { + case NFPROTO_IPV4: + stmt->reject.family = dl->pctx.family; +- datatype_set(stmt->reject.expr, &icmp_code_type); ++ datatype_set(stmt->reject.expr, &reject_icmp_code_type); + if (stmt->reject.type == NFT_REJECT_TCP_RST && + payload_dependency_exists(&dl->pdctx, + PROTO_BASE_TRANSPORT_HDR)) +@@ -2939,7 +2939,7 @@ static void stmt_reject_postprocess(struct rule_pp_ctx *rctx) + break; + case NFPROTO_IPV6: + stmt->reject.family = dl->pctx.family; +- datatype_set(stmt->reject.expr, &icmpv6_code_type); ++ datatype_set(stmt->reject.expr, &reject_icmpv6_code_type); + if (stmt->reject.type == NFT_REJECT_TCP_RST && + payload_dependency_exists(&dl->pdctx, + PROTO_BASE_TRANSPORT_HDR)) +@@ -2950,7 +2950,7 @@ static void stmt_reject_postprocess(struct rule_pp_ctx *rctx) + case NFPROTO_BRIDGE: + case NFPROTO_NETDEV: + if (stmt->reject.type == NFT_REJECT_ICMPX_UNREACH) { +- datatype_set(stmt->reject.expr, &icmpx_code_type); ++ datatype_set(stmt->reject.expr, &reject_icmpx_code_type); + break; + } + +@@ -2966,12 +2966,12 @@ static void stmt_reject_postprocess(struct rule_pp_ctx *rctx) + case NFPROTO_IPV4: /* INET */ + case __constant_htons(ETH_P_IP): /* BRIDGE, NETDEV */ + stmt->reject.family = NFPROTO_IPV4; +- datatype_set(stmt->reject.expr, &icmp_code_type); ++ datatype_set(stmt->reject.expr, &reject_icmp_code_type); + break; + case NFPROTO_IPV6: /* INET */ + case __constant_htons(ETH_P_IPV6): /* BRIDGE, NETDEV */ + stmt->reject.family = NFPROTO_IPV6; +- datatype_set(stmt->reject.expr, &icmpv6_code_type); ++ datatype_set(stmt->reject.expr, &reject_icmpv6_code_type); + break; + default: + break; +diff --git a/src/parser_bison.y b/src/parser_bison.y +index 23b64ce..e3ac2fb 100644 +--- a/src/parser_bison.y ++++ b/src/parser_bison.y +@@ -3715,40 +3715,40 @@ reject_opts : /* empty */ + $0->reject.family = NFPROTO_IPV4; + $0->reject.type = NFT_REJECT_ICMP_UNREACH; + $0->reject.expr = $4; +- datatype_set($0->reject.expr, &icmp_code_type); ++ datatype_set($0->reject.expr, &reject_icmp_code_type); + } + | WITH ICMP reject_with_expr + { + $0->reject.family = NFPROTO_IPV4; + $0->reject.type = NFT_REJECT_ICMP_UNREACH; + $0->reject.expr = $3; +- datatype_set($0->reject.expr, &icmp_code_type); ++ datatype_set($0->reject.expr, &reject_icmp_code_type); + } + | WITH ICMP6 TYPE reject_with_expr close_scope_type close_scope_icmp + { + $0->reject.family = NFPROTO_IPV6; + $0->reject.type = NFT_REJECT_ICMP_UNREACH; + $0->reject.expr = $4; +- datatype_set($0->reject.expr, &icmpv6_code_type); ++ datatype_set($0->reject.expr, &reject_icmpv6_code_type); + } + | WITH ICMP6 reject_with_expr + { + $0->reject.family = NFPROTO_IPV6; + $0->reject.type = NFT_REJECT_ICMP_UNREACH; + $0->reject.expr = $3; +- datatype_set($0->reject.expr, &icmpv6_code_type); ++ datatype_set($0->reject.expr, &reject_icmpv6_code_type); + } + | WITH ICMPX TYPE reject_with_expr close_scope_type + { + $0->reject.type = NFT_REJECT_ICMPX_UNREACH; + $0->reject.expr = $4; +- datatype_set($0->reject.expr, &icmpx_code_type); ++ datatype_set($0->reject.expr, &reject_icmpx_code_type); + } + | WITH ICMPX reject_with_expr + { + $0->reject.type = NFT_REJECT_ICMPX_UNREACH; + $0->reject.expr = $3; +- datatype_set($0->reject.expr, &icmpx_code_type); ++ datatype_set($0->reject.expr, &reject_icmpx_code_type); + } + | WITH TCP close_scope_tcp RESET close_scope_reset + { +diff --git a/src/parser_json.c b/src/parser_json.c +index b8ed848..25483b1 100644 +--- a/src/parser_json.c ++++ b/src/parser_json.c +@@ -2318,17 +2318,17 @@ static struct stmt *json_parse_reject_stmt(struct json_ctx *ctx, + stmt->reject.icmp_code = 0; + } else if (!strcmp(type, "icmpx")) { + stmt->reject.type = NFT_REJECT_ICMPX_UNREACH; +- dtype = &icmpx_code_type; ++ dtype = &reject_icmpx_code_type; + stmt->reject.icmp_code = 0; + } else if (!strcmp(type, "icmp")) { + stmt->reject.type = NFT_REJECT_ICMP_UNREACH; + stmt->reject.family = NFPROTO_IPV4; +- dtype = &icmp_code_type; ++ dtype = &reject_icmp_code_type; + stmt->reject.icmp_code = 0; + } else if (!strcmp(type, "icmpv6")) { + stmt->reject.type = NFT_REJECT_ICMP_UNREACH; + stmt->reject.family = NFPROTO_IPV6; +- dtype = &icmpv6_code_type; ++ dtype = &reject_icmpv6_code_type; + stmt->reject.icmp_code = 0; + } + } +diff --git a/tests/py/ip/icmp.t b/tests/py/ip/icmp.t +index 7ddf8b3..226c339 100644 +--- a/tests/py/ip/icmp.t ++++ b/tests/py/ip/icmp.t +@@ -26,8 +26,8 @@ icmp code 111 accept;ok + icmp code != 111 accept;ok + icmp code 33-55;ok + icmp code != 33-55;ok +-icmp code { 2, 4, 54, 33, 56};ok;icmp code { prot-unreachable, frag-needed, 33, 54, 56} +-icmp code != { prot-unreachable, frag-needed, 33, 54, 56};ok ++icmp code { 2, 4, 54, 33, 56};ok ++icmp code != { prot-unreachable, frag-needed, 33, 54, 56};ok;icmp code != { 2, 4, 33, 54, 56} + + icmp checksum 12343 accept;ok + icmp checksum != 12343 accept;ok +@@ -73,5 +73,5 @@ icmp gateway != { 33, 55, 67, 88};ok + icmp gateway != 34;ok + icmp gateway != { 333, 334};ok + +-icmp code 1 icmp type 2;ok;icmp type 2 icmp code host-unreachable ++icmp code 1 icmp type 2;ok;icmp type 2 icmp code 1 + icmp code != 1 icmp type 2 icmp mtu 5;fail +diff --git a/tests/py/ip6/icmpv6.t b/tests/py/ip6/icmpv6.t +index 35dad2b..7632bfd 100644 +--- a/tests/py/ip6/icmpv6.t ++++ b/tests/py/ip6/icmpv6.t +@@ -28,10 +28,10 @@ icmpv6 type {router-renumbering, mld-listener-done, time-exceeded, nd-router-sol + icmpv6 type {mld-listener-query, time-exceeded, nd-router-advert} accept;ok + icmpv6 type != {mld-listener-query, time-exceeded, nd-router-advert} accept;ok + +-icmpv6 code 4;ok;icmpv6 code port-unreachable ++icmpv6 code 4;ok + icmpv6 code 3-66;ok +-icmpv6 code {5, 6, 7} accept;ok;icmpv6 code {policy-fail, reject-route, 7} accept +-icmpv6 code != {policy-fail, reject-route, 7} accept;ok ++icmpv6 code {5, 6, 7} accept;ok ++icmpv6 code != {policy-fail, reject-route, 7} accept;ok;icmpv6 code != {5, 6, 7} accept + + icmpv6 checksum 2222 log;ok + icmpv6 checksum != 2222 log;ok +@@ -84,7 +84,7 @@ icmpv6 max-delay != 33-45;ok + icmpv6 max-delay {33, 55, 67, 88};ok + icmpv6 max-delay != {33, 55, 67, 88};ok + +-icmpv6 type parameter-problem icmpv6 code no-route;ok ++icmpv6 type parameter-problem icmpv6 code 0;ok + + icmpv6 type mld-listener-query icmpv6 taddr 2001:db8::133;ok + icmpv6 type nd-neighbor-solicit icmpv6 taddr 2001:db8::133;ok diff --git a/0086-tests-py-complete-icmp-and-icmpv6-update.patch b/0086-tests-py-complete-icmp-and-icmpv6-update.patch new file mode 100644 index 0000000..7d13a54 --- /dev/null +++ b/0086-tests-py-complete-icmp-and-icmpv6-update.patch @@ -0,0 +1,146 @@ +From 7a57053ed7c9fa767596b33521545c552be89dba Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:43 +0200 +Subject: [PATCH] tests: py: complete icmp and icmpv6 update + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 1702bfd70b39fe955d5e2c4b93f7dc07acb79027 + +commit 1702bfd70b39fe955d5e2c4b93f7dc07acb79027 +Author: Pablo Neira Ayuso +Date: Thu Apr 4 13:56:39 2024 +0200 + + tests: py: complete icmp and icmpv6 update + + Update json update and leftover payload update to complete + 5fecd2a6ef61 ("src: disentangle ICMP code types"). + + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + tests/py/ip/icmp.t.json | 6 +++--- + tests/py/ip/icmp.t.json.output | 4 ++-- + tests/py/ip6/icmpv6.t.json | 8 ++++---- + tests/py/ip6/icmpv6.t.json.output | 8 ++++---- + tests/py/ip6/icmpv6.t.payload.ip6 | 2 +- + 5 files changed, 14 insertions(+), 14 deletions(-) + +diff --git a/tests/py/ip/icmp.t.json b/tests/py/ip/icmp.t.json +index 4f05250..45e04c7 100644 +--- a/tests/py/ip/icmp.t.json ++++ b/tests/py/ip/icmp.t.json +@@ -459,8 +459,8 @@ + "op": "!=", + "right": { + "set": [ +- "prot-unreachable", +- "frag-needed", ++ 2, ++ 4, + 33, + 54, + 56 +@@ -1488,7 +1488,7 @@ + } + }, + "op": "==", +- "right": "host-unreachable" ++ "right": 1 + } + } + ] +diff --git a/tests/py/ip/icmp.t.json.output b/tests/py/ip/icmp.t.json.output +index 5a07585..52fd601 100644 +--- a/tests/py/ip/icmp.t.json.output ++++ b/tests/py/ip/icmp.t.json.output +@@ -11,8 +11,8 @@ + "op": "==", + "right": { + "set": [ +- "prot-unreachable", +- "frag-needed", ++ 2, ++ 4, + 33, + 54, + 56 +diff --git a/tests/py/ip6/icmpv6.t.json b/tests/py/ip6/icmpv6.t.json +index 224a8e8..9df886d 100644 +--- a/tests/py/ip6/icmpv6.t.json ++++ b/tests/py/ip6/icmpv6.t.json +@@ -532,8 +532,8 @@ + "op": "!=", + "right": { + "set": [ +- "policy-fail", +- "reject-route", ++ 5, ++ 6, + 7 + ] + } +@@ -1136,7 +1136,7 @@ + } + ] + +-# icmpv6 type parameter-problem icmpv6 code no-route ++# icmpv6 type parameter-problem icmpv6 code 0 + [ + { + "match": { +@@ -1159,7 +1159,7 @@ + } + }, + "op": "==", +- "right": "no-route" ++ "right": 0 + } + } + ] +diff --git a/tests/py/ip6/icmpv6.t.json.output b/tests/py/ip6/icmpv6.t.json.output +index 7b8f5c1..f29b346 100644 +--- a/tests/py/ip6/icmpv6.t.json.output ++++ b/tests/py/ip6/icmpv6.t.json.output +@@ -104,7 +104,7 @@ + } + }, + "op": "==", +- "right": "port-unreachable" ++ "right": 4 + } + } + ] +@@ -122,7 +122,7 @@ + "op": "==", + "right": { + "range": [ +- "addr-unreachable", ++ 3, + 66 + ] + } +@@ -143,8 +143,8 @@ + "op": "==", + "right": { + "set": [ +- "policy-fail", +- "reject-route", ++ 5, ++ 6, + 7 + ] + } +diff --git a/tests/py/ip6/icmpv6.t.payload.ip6 b/tests/py/ip6/icmpv6.t.payload.ip6 +index fcaf481..5b6035d 100644 +--- a/tests/py/ip6/icmpv6.t.payload.ip6 ++++ b/tests/py/ip6/icmpv6.t.payload.ip6 +@@ -554,7 +554,7 @@ ip6 test-ip6 input + [ payload load 2b @ transport header + 4 => reg 1 ] + [ lookup reg 1 set __set%d 0x1 ] + +-# icmpv6 type parameter-problem icmpv6 code no-route ++# icmpv6 type parameter-problem icmpv6 code 0 + ip6 + [ meta load l4proto => reg 1 ] + [ cmp eq reg 1 0x0000003a ] diff --git a/0087-tests-shell-payload-matching-requires-egress-support.patch b/0087-tests-shell-payload-matching-requires-egress-support.patch new file mode 100644 index 0000000..a7dc00b --- /dev/null +++ b/0087-tests-shell-payload-matching-requires-egress-support.patch @@ -0,0 +1,36 @@ +From 1b8794fe549b6e242b99eebf477a9508e25e6ac4 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:43 +0200 +Subject: [PATCH] tests: shell: payload matching requires egress support + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit b13c0379bcf11caf64734240ec8fbcaa668c4016 + +commit b13c0379bcf11caf64734240ec8fbcaa668c4016 +Author: Pablo Neira Ayuso +Date: Mon Apr 8 20:25:12 2024 +0200 + + tests: shell: payload matching requires egress support + + Older kernels do not support for egress hook. + + Fixes: 84da729e067a ("tests: shell: add test to cover payload transport match and mangle") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + tests/shell/testcases/packetpath/payload | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/tests/shell/testcases/packetpath/payload b/tests/shell/testcases/packetpath/payload +index 9f4587d..4c5c42d 100755 +--- a/tests/shell/testcases/packetpath/payload ++++ b/tests/shell/testcases/packetpath/payload +@@ -1,5 +1,7 @@ + #!/bin/bash + ++# NFT_TEST_REQUIRES(NFT_TEST_HAVE_netdev_egress) ++ + rnd=$(mktemp -u XXXXXXXX) + ns1="nft1payload-$rnd" + ns2="nft2payload-$rnd" diff --git a/0088-tests-shell-check-for-reset-tcp-options-support.patch b/0088-tests-shell-check-for-reset-tcp-options-support.patch new file mode 100644 index 0000000..5046afb --- /dev/null +++ b/0088-tests-shell-check-for-reset-tcp-options-support.patch @@ -0,0 +1,47 @@ +From 23fdf421ab37132dae2ed324f1e6d6f634d82f56 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:43 +0200 +Subject: [PATCH] tests: shell: check for reset tcp options support + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 6a39fd3b68ac8a96340b87b9dc8380a9cd4e6398 + +commit 6a39fd3b68ac8a96340b87b9dc8380a9cd4e6398 +Author: Pablo Neira Ayuso +Date: Mon Apr 8 23:05:28 2024 +0200 + + tests: shell: check for reset tcp options support + + Fixes: 59a33d08ab3a ("parser: tcpopt: fix tcp option parsing with NUM + length field") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + tests/shell/features/reset_tcp_options.nft | 5 +++++ + tests/shell/testcases/packetpath/tcp_options | 2 ++ + 2 files changed, 7 insertions(+) + create mode 100644 tests/shell/features/reset_tcp_options.nft + +diff --git a/tests/shell/features/reset_tcp_options.nft b/tests/shell/features/reset_tcp_options.nft +new file mode 100644 +index 0000000..47d1c7b +--- /dev/null ++++ b/tests/shell/features/reset_tcp_options.nft +@@ -0,0 +1,5 @@ ++table inet t { ++ chain c { ++ reset tcp option fastopen ++ } ++} +diff --git a/tests/shell/testcases/packetpath/tcp_options b/tests/shell/testcases/packetpath/tcp_options +index 8855222..57e228c 100755 +--- a/tests/shell/testcases/packetpath/tcp_options ++++ b/tests/shell/testcases/packetpath/tcp_options +@@ -1,5 +1,7 @@ + #!/bin/bash + ++# NFT_TEST_REQUIRES(NFT_TEST_HAVE_reset_tcp_options) ++ + have_socat="no" + socat -h > /dev/null && have_socat="yes" + diff --git a/0089-doc-nft.8-Two-minor-synopsis-fixups.patch b/0089-doc-nft.8-Two-minor-synopsis-fixups.patch new file mode 100644 index 0000000..a02332f --- /dev/null +++ b/0089-doc-nft.8-Two-minor-synopsis-fixups.patch @@ -0,0 +1,51 @@ +From dd80c4580464f4eb2f27b10d5a42f0e95499383d Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:43 +0200 +Subject: [PATCH] doc: nft.8: Two minor synopsis fixups + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 3bccc478d27498f7ecc8a0233176accb1b91f584 + +commit 3bccc478d27498f7ecc8a0233176accb1b91f584 +Author: Phil Sutter +Date: Thu Mar 21 14:57:41 2024 +0100 + + doc: nft.8: Two minor synopsis fixups + + The curly braces in 'add table' are to be put literally, so need to be + bold. Also, they are optional unless either one (or both) of 'comment' + and 'flags' are specified. + + The 'add chain' synopsis contained a stray tick, messing up the + following markup. + + Fixes: 7fd67ce121f86 ("doc: fix synopsis of named counter, quota and ct {helper,timeout,expect}") + Signed-off-by: Phil Sutter + +Signed-off-by: Phil Sutter +--- + doc/nft.txt | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/doc/nft.txt b/doc/nft.txt +index dba1b60..2080c07 100644 +--- a/doc/nft.txt ++++ b/doc/nft.txt +@@ -321,7 +321,7 @@ Effectively, this is the nft-equivalent of *iptables-save* and + TABLES + ------ + [verse] +-{*add* | *create*} *table* ['family'] 'table' [ {*comment* 'comment' *;*'} *{ flags* 'flags' *; }*] ++{*add* | *create*} *table* ['family'] 'table' [*{* [*comment* 'comment' *;*] [*flags* 'flags' *;*] *}*] + {*delete* | *destroy* | *list* | *flush*} *table* ['family'] 'table' + *list tables* ['family'] + *delete table* ['family'] *handle* 'handle' +@@ -385,7 +385,7 @@ add table inet mytable + CHAINS + ------ + [verse] +-{*add* | *create*} *chain* ['family'] 'table' 'chain' [*{ type* 'type' *hook* 'hook' [*device* 'device'] *priority* 'priority' *;* [*policy* 'policy' *;*] [*comment* 'comment' *;*'] *}*] ++{*add* | *create*} *chain* ['family'] 'table' 'chain' [*{ type* 'type' *hook* 'hook' [*device* 'device'] *priority* 'priority' *;* [*policy* 'policy' *;*] [*comment* 'comment' *;*] *}*] + {*delete* | *destroy* | *list* | *flush*} *chain* ['family'] 'table' 'chain' + *list chains* ['family'] + *delete chain* ['family'] 'table' *handle* 'handle' diff --git a/0090-mergesort-Avoid-accidental-set-element-reordering.patch b/0090-mergesort-Avoid-accidental-set-element-reordering.patch new file mode 100644 index 0000000..4ea26b6 --- /dev/null +++ b/0090-mergesort-Avoid-accidental-set-element-reordering.patch @@ -0,0 +1,94 @@ +From 1e8d5235abba36fde530b3613cbea195c4f927f7 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:08:43 +0200 +Subject: [PATCH] mergesort: Avoid accidental set element reordering + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit bf52af188b306acf5a30134d6a670f41f16a9459 +Conflicts: Dropped changes to non-existent .json-nft dump + +commit bf52af188b306acf5a30134d6a670f41f16a9459 +Author: Phil Sutter +Date: Fri Mar 22 13:31:10 2024 +0100 + + mergesort: Avoid accidental set element reordering + + In corner cases, expr_msort_cmp() may return 0 for two non-identical + elements. An example are ORed tcp flags: 'syn' and 'syn | ack' are + considered the same value since expr_msort_value() reduces the latter to + its LHS. + + Keeping the above in mind and looking at how list_expr_sort() works: The + list in 'head' is cut in half, the first half put into the temporary + list 'list' and finally 'list' is merged back into 'head' considering + each element's position. Shall expr_msort_cmp() return 0 for two + elements, the one from 'list' ends up after the one in 'head', thus + reverting their previous ordering. + + The practical implication is that output never matches input for the + sample set '{ syn, syn | ack }' as the sorting after delinearization in + netlink_list_setelems() keeps swapping the elements. Out of coincidence, + the commit this fixes itself illustrates the use-case this breaks, + namely tracking a ruleset in git: Each ruleset reload will trigger an + update to the stored dump. + + This change breaks interval set element deletion because __set_delete() + implicitly relies upon this reordering of duplicate entries by inserting + a clone of the one to delete into the start (via list_move()) and after + sorting assumes the clone will end up right behind the original. Fix + this by calling list_move_tail() instead. + + Fixes: 14ee0a979b622 ("src: sort set elements in netlink_get_setelems()") + Signed-off-by: Phil Sutter + +Signed-off-by: Phil Sutter +--- + src/intervals.c | 2 +- + src/mergesort.c | 2 +- + tests/shell/testcases/sets/dumps/0055tcpflags_0.nft | 8 ++++---- + 3 files changed, 6 insertions(+), 6 deletions(-) + +diff --git a/src/intervals.c b/src/intervals.c +index 85de019..d5406ef 100644 +--- a/src/intervals.c ++++ b/src/intervals.c +@@ -461,7 +461,7 @@ static int __set_delete(struct list_head *msgs, struct expr *i, struct set *set, + unsigned int debug_mask) + { + i->flags |= EXPR_F_REMOVE; +- list_move(&i->list, &existing_set->init->expressions); ++ list_move_tail(&i->list, &existing_set->init->expressions); + list_expr_sort(&existing_set->init->expressions); + + return setelem_delete(msgs, set, init, existing_set->init, debug_mask); +diff --git a/src/mergesort.c b/src/mergesort.c +index 4d0e280..5e676be 100644 +--- a/src/mergesort.c ++++ b/src/mergesort.c +@@ -78,7 +78,7 @@ void list_splice_sorted(struct list_head *list, struct list_head *head) + while (l != list) { + if (h == head || + expr_msort_cmp(list_entry(l, typeof(struct expr), list), +- list_entry(h, typeof(struct expr), list)) < 0) { ++ list_entry(h, typeof(struct expr), list)) <= 0) { + l = l->next; + list_add_tail(l->prev, h); + continue; +diff --git a/tests/shell/testcases/sets/dumps/0055tcpflags_0.nft b/tests/shell/testcases/sets/dumps/0055tcpflags_0.nft +index ffed542..22bf5c4 100644 +--- a/tests/shell/testcases/sets/dumps/0055tcpflags_0.nft ++++ b/tests/shell/testcases/sets/dumps/0055tcpflags_0.nft +@@ -2,9 +2,9 @@ table ip test { + set tcp_good_flags { + type tcp_flag + flags constant +- elements = { fin | psh | ack | urg, fin | psh | ack, fin | ack | urg, fin | ack, syn | psh | ack | urg, +- syn | psh | ack, syn | ack | urg, syn | ack, syn, rst | psh | ack | urg, +- rst | psh | ack, rst | ack | urg, rst | ack, rst, psh | ack | urg, +- psh | ack, ack | urg, ack } ++ elements = { fin | ack, fin | ack | urg, fin | psh | ack, fin | psh | ack | urg, syn, ++ syn | ack, syn | ack | urg, syn | psh | ack, syn | psh | ack | urg, rst, ++ rst | ack, rst | ack | urg, rst | psh | ack, rst | psh | ack | urg, psh | ack, ++ psh | ack | urg, ack, ack | urg } + } + } diff --git a/0091-doc-nft.8-Fix-markup-in-ct-expectation-synopsis.patch b/0091-doc-nft.8-Fix-markup-in-ct-expectation-synopsis.patch new file mode 100644 index 0000000..6bedc3b --- /dev/null +++ b/0091-doc-nft.8-Fix-markup-in-ct-expectation-synopsis.patch @@ -0,0 +1,37 @@ +From 481a7da59f20b9c1322a5d26a9bce3e94f04443d Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:09:25 +0200 +Subject: [PATCH] doc: nft.8: Fix markup in ct expectation synopsis + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit ef659df2a45c38ad18f703febeae8c4febf9dd04 + +commit ef659df2a45c38ad18f703febeae8c4febf9dd04 +Author: Phil Sutter +Date: Wed Apr 24 23:46:11 2024 +0200 + + doc: nft.8: Fix markup in ct expectation synopsis + + Just a missing asterisk somewhere. + + Fixes: 1dd08fcfa07a4 ("src: add ct expectations support") + Signed-off-by: Phil Sutter + +Signed-off-by: Phil Sutter +--- + doc/stateful-objects.txt | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/doc/stateful-objects.txt b/doc/stateful-objects.txt +index 00d3c5f..5824d53 100644 +--- a/doc/stateful-objects.txt ++++ b/doc/stateful-objects.txt +@@ -119,7 +119,7 @@ sport=41360 dport=22 + CT EXPECTATION + ~~~~~~~~~~~~~~ + [verse] +-*add* *ct expectation* ['family'] 'table' 'name' *{ protocol* 'protocol' *; dport* 'dport' *; timeout* 'timeout' *; size* 'size' *; [*l3proto* 'family' *;*] *}* ++*add* *ct expectation* ['family'] 'table' 'name' *{ protocol* 'protocol' *; dport* 'dport' *; timeout* 'timeout' *; size* 'size' *;* [*l3proto* 'family' *;*] *}* + *delete* *ct expectation* ['family'] 'table' 'name' + *list* *ct expectations* + diff --git a/0092-cache-check-for-NFT_CACHE_REFRESH-in-current-request.patch b/0092-cache-check-for-NFT_CACHE_REFRESH-in-current-request.patch new file mode 100644 index 0000000..a323e6d --- /dev/null +++ b/0092-cache-check-for-NFT_CACHE_REFRESH-in-current-request.patch @@ -0,0 +1,76 @@ +From 694638b161288c479b28005db67c403903182a66 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:09:25 +0200 +Subject: [PATCH] cache: check for NFT_CACHE_REFRESH in current requested cache + too + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit bbb0e944b59d355085e8f50b4b7b5057ae0d33a4 + +commit bbb0e944b59d355085e8f50b4b7b5057ae0d33a4 +Author: Pablo Neira Ayuso +Date: Mon May 27 18:12:05 2024 +0200 + + cache: check for NFT_CACHE_REFRESH in current requested cache too + + NFT_CACHE_REFRESH is set on inconditionally by ruleset list commands to + deal with stateful information in this ruleset. This flag results in + dropping the existing cache and fully fetching all objects from the + kernel. + + Set on this flag for reset commands too, this is missing. + + List/reset commands allow for filtering by specific family and object, + therefore, NFT_CACHE_REFRESH also signals that the cache is partially + populated. + + Check if this flag is requested by the current list/reset command, as + well as cache->flags which represents the cache after the _previous_ + list of commands. + + A follow up patch allows to recycle the existing cache if the flags + report that the same objects are already available in the cache, + NFT_CACHE_REFRESH is useful to report that cache cannot be recycled. + + Fixes: 407c54f71255 ("src: cache gets out of sync in interactive mode") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/cache.c | 8 +++++--- + 1 file changed, 5 insertions(+), 3 deletions(-) + +diff --git a/src/cache.c b/src/cache.c +index c000e32..e88cbae 100644 +--- a/src/cache.c ++++ b/src/cache.c +@@ -297,6 +297,7 @@ static unsigned int evaluate_cache_reset(struct cmd *cmd, unsigned int flags, + flags |= NFT_CACHE_TABLE; + break; + } ++ flags |= NFT_CACHE_REFRESH; + + return flags; + } +@@ -1177,9 +1178,10 @@ static bool nft_cache_is_complete(struct nft_cache *cache, unsigned int flags) + return (cache->flags & flags) == flags; + } + +-static bool nft_cache_needs_refresh(struct nft_cache *cache) ++static bool nft_cache_needs_refresh(struct nft_cache *cache, unsigned int flags) + { +- return cache->flags & NFT_CACHE_REFRESH; ++ return (cache->flags & NFT_CACHE_REFRESH) || ++ (flags & NFT_CACHE_REFRESH); + } + + static bool nft_cache_is_updated(struct nft_cache *cache, uint16_t genid) +@@ -1207,7 +1209,7 @@ int nft_cache_update(struct nft_ctx *nft, unsigned int flags, + replay: + ctx.seqnum = cache->seqnum++; + genid = mnl_genid_get(&ctx); +- if (!nft_cache_needs_refresh(cache) && ++ if (!nft_cache_needs_refresh(cache, flags) && + nft_cache_is_complete(cache, flags) && + nft_cache_is_updated(cache, genid)) + return 0; diff --git a/0093-evaluate-bogus-protocol-conflicts-in-vlan-with-impli.patch b/0093-evaluate-bogus-protocol-conflicts-in-vlan-with-impli.patch new file mode 100644 index 0000000..88a31eb --- /dev/null +++ b/0093-evaluate-bogus-protocol-conflicts-in-vlan-with-impli.patch @@ -0,0 +1,227 @@ +From da2b7b622c703aafa4fb42442b5b74da9828f842 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:09:25 +0200 +Subject: [PATCH] evaluate: bogus protocol conflicts in vlan with implicit + dependencies + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit d1a7e74d1e065d244439fdb0f1c1cba83f921609 + +commit d1a7e74d1e065d244439fdb0f1c1cba83f921609 +Author: Pablo Neira Ayuso +Date: Wed May 15 19:23:49 2024 +0200 + + evaluate: bogus protocol conflicts in vlan with implicit dependencies + + The following command: + + # nft add rule netdev x y ip saddr 10.1.1.1 icmp type echo-request vlan id set 321 + + fails with: + + Error: conflicting link layer protocols specified: ether vs. vlan + netdev x y ip saddr 10.1.1.1 icmp type echo-request vlan id set 321 + ^^^^^^^ + + Users can work around this issue by prepending an explicit match for + vlan ethertype field, that is: + + ether type vlan ip saddr 10.1.1.1 ... + ^-------------^ + + but nft should really handle this itself. + + The error above is triggered by the following check in + resolve_ll_protocol_conflict(): + + /* This payload and the existing context don't match, conflict. */ + if (pctx->protocol[base + 1].desc != NULL) + return 1; + + This check was added by 39f15c243912 ("nft: support listing expressions + that use non-byte header fields") and f7d5590688a6 ("tests: vlan tests") + to deal with conflicting link layer protocols, for instance: + + ether type ip vlan id 1 + + this is matching ethertype ip at offset 12, but then it matches for vlan + id at offset 14 which is not present given the previous check. + + One possibility is to remove such check, but nft does not bail out for + the example above and it results in bytecode that never matches: + + # nft --debug=netlink netdev x y ether type ip vlan id 10 + netdev x y + [ meta load iiftype => reg 1 ] + [ cmp eq reg 1 0x00000001 ] + [ payload load 2b @ link header + 12 => reg 1 ] <---- ether type + [ cmp eq reg 1 0x00000008 ] <---- ip + [ payload load 2b @ link header + 12 => reg 1 ] <---- ether type + [ cmp eq reg 1 0x00000081 ] <---- vlan + [ payload load 2b @ link header + 14 => reg 1 ] + [ bitwise reg 1 = ( reg 1 & 0x0000ff0f ) ^ 0x00000000 ] + [ cmp eq reg 1 0x00000a00 ] + + This is due to resolve_ll_protocol_conflict() which deals with the + conflict by updating protocol context and emitting an implicit + dependency, but there is already an explicit match coming from the user. + + This patch adds a new helper function to check if an implicit dependency + clashes with an existing statement, which results in: + + # nft add rule netdev x y ether type ip vlan id 1 + Error: conflicting statements + add rule netdev x y ether type ip vlan id 1 + ^^^^^^^^^^^^^ ~~~~~~~ + + Theoretically, no duplicated implicit dependency should ever be emitted + if protocol context is correctly handled. + + Only implicit payload expressions are considered at this stage for this + conflict check, this patch can be extended to deal with other dependency + types. + + Fixes: 39f15c243912 ("nft: support listing expressions that use non-byte header fields") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/evaluate.c | 69 +++++++++++++++++++++++++++++++++++++++++--------- + 1 file changed, 57 insertions(+), 12 deletions(-) + +diff --git a/src/evaluate.c b/src/evaluate.c +index 38a80f2..d8e5dfd 100644 +--- a/src/evaluate.c ++++ b/src/evaluate.c +@@ -759,6 +759,46 @@ static bool proto_is_dummy(const struct proto_desc *desc) + return desc == &proto_inet || desc == &proto_netdev; + } + ++static int stmt_dep_conflict(struct eval_ctx *ctx, const struct stmt *nstmt) ++{ ++ struct stmt *stmt; ++ ++ list_for_each_entry(stmt, &ctx->rule->stmts, list) { ++ if (stmt == nstmt) ++ break; ++ ++ if (stmt->ops->type != STMT_EXPRESSION || ++ stmt->expr->etype != EXPR_RELATIONAL || ++ stmt->expr->right->etype != EXPR_VALUE || ++ stmt->expr->left->etype != EXPR_PAYLOAD || ++ stmt->expr->left->etype != nstmt->expr->left->etype || ++ stmt->expr->left->len != nstmt->expr->left->len) ++ continue; ++ ++ if (stmt->expr->left->payload.desc != nstmt->expr->left->payload.desc || ++ stmt->expr->left->payload.inner_desc != nstmt->expr->left->payload.inner_desc || ++ stmt->expr->left->payload.base != nstmt->expr->left->payload.base || ++ stmt->expr->left->payload.offset != nstmt->expr->left->payload.offset) ++ continue; ++ ++ return stmt_binary_error(ctx, stmt, nstmt, ++ "conflicting statements"); ++ } ++ ++ return 0; ++} ++ ++static int rule_stmt_dep_add(struct eval_ctx *ctx, ++ struct stmt *nstmt, struct stmt *stmt) ++{ ++ rule_stmt_insert_at(ctx->rule, nstmt, ctx->stmt); ++ ++ if (stmt_dep_conflict(ctx, nstmt) < 0) ++ return -1; ++ ++ return 0; ++} ++ + static int resolve_ll_protocol_conflict(struct eval_ctx *ctx, + const struct proto_desc *desc, + struct expr *payload) +@@ -782,7 +822,8 @@ static int resolve_ll_protocol_conflict(struct eval_ctx *ctx, + return err; + + desc = payload->payload.desc; +- rule_stmt_insert_at(ctx->rule, nstmt, ctx->stmt); ++ if (rule_stmt_dep_add(ctx, nstmt, ctx->stmt) < 0) ++ return -1; + } + } else { + unsigned int i; +@@ -794,10 +835,6 @@ static int resolve_ll_protocol_conflict(struct eval_ctx *ctx, + } + } + +- /* This payload and the existing context don't match, conflict. */ +- if (pctx->protocol[base + 1].desc != NULL) +- return 1; +- + link = proto_find_num(desc, payload->payload.desc); + if (link < 0 || + ll_conflict_resolution_gen_dependency(ctx, link, payload, &nstmt) < 0) +@@ -806,7 +843,8 @@ static int resolve_ll_protocol_conflict(struct eval_ctx *ctx, + for (i = 0; i < pctx->stacked_ll_count; i++) + payload->payload.offset += pctx->stacked_ll[i]->length; + +- rule_stmt_insert_at(ctx->rule, nstmt, ctx->stmt); ++ if (rule_stmt_dep_add(ctx, nstmt, ctx->stmt) < 0) ++ return -1; + + return 0; + } +@@ -834,7 +872,8 @@ static int __expr_evaluate_payload(struct eval_ctx *ctx, struct expr *expr) + if (payload_gen_dependency(ctx, payload, &nstmt) < 0) + return -1; + +- rule_stmt_insert_at(ctx->rule, nstmt, ctx->stmt); ++ if (rule_stmt_dep_add(ctx, nstmt, ctx->stmt) < 0) ++ return -1; + + desc = pctx->protocol[base].desc; + +@@ -854,7 +893,10 @@ static int __expr_evaluate_payload(struct eval_ctx *ctx, struct expr *expr) + + assert(pctx->stacked_ll_count); + payload->payload.offset += pctx->stacked_ll[0]->length; +- rule_stmt_insert_at(ctx->rule, nstmt, ctx->stmt); ++ ++ if (rule_stmt_dep_add(ctx, nstmt, ctx->stmt) < 0) ++ return -1; ++ + return 1; + } + goto check_icmp; +@@ -895,8 +937,8 @@ check_icmp: + if (payload_gen_icmp_dependency(ctx, expr, &nstmt) < 0) + return -1; + +- if (nstmt) +- rule_stmt_insert_at(ctx->rule, nstmt, ctx->stmt); ++ if (nstmt && rule_stmt_dep_add(ctx, nstmt, ctx->stmt) < 0) ++ return -1; + + return 0; + } +@@ -968,7 +1010,8 @@ static int expr_evaluate_inner(struct eval_ctx *ctx, struct expr **exprp) + if (payload_gen_inner_dependency(ctx, expr, &nstmt) < 0) + return -1; + +- rule_stmt_insert_at(ctx->rule, nstmt, ctx->stmt); ++ if (rule_stmt_dep_add(ctx, nstmt, ctx->stmt) < 0) ++ return -1; + + proto_ctx_update(pctx, PROTO_BASE_TRANSPORT_HDR, &expr->location, expr->payload.inner_desc); + } +@@ -1099,7 +1142,9 @@ static int ct_gen_nh_dependency(struct eval_ctx *ctx, struct expr *ct) + relational_expr_pctx_update(pctx, dep); + + nstmt = expr_stmt_alloc(&dep->location, dep); +- rule_stmt_insert_at(ctx->rule, nstmt, ctx->stmt); ++ ++ if (rule_stmt_dep_add(ctx, nstmt, ctx->stmt) < 0) ++ return -1; + + return 0; + } diff --git a/0094-evaluate-Fix-incorrect-checking-the-base-variable-in.patch b/0094-evaluate-Fix-incorrect-checking-the-base-variable-in.patch new file mode 100644 index 0000000..68a6c90 --- /dev/null +++ b/0094-evaluate-Fix-incorrect-checking-the-base-variable-in.patch @@ -0,0 +1,39 @@ +From e4df7c0617f8ed9d9ef2afcbd5acd398f7582b7d Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:09:25 +0200 +Subject: [PATCH] evaluate: Fix incorrect checking the `base` variable in case + of IPV6 + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit f6b579344eee17e5587b6a7fcc444fe997cd8cb6 + +commit f6b579344eee17e5587b6a7fcc444fe997cd8cb6 +Author: Maks Mishin +Date: Wed May 15 23:25:03 2024 +0300 + + evaluate: Fix incorrect checking the `base` variable in case of IPV6 + + Found by RASU JSC. + + Fixes: 2b29ea5f3c3e ("src: ct: add eval part to inject dependencies for ct saddr/daddr") + Signed-off-by: Maks Mishin + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/evaluate.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/evaluate.c b/src/evaluate.c +index d8e5dfd..7f5b64b 100644 +--- a/src/evaluate.c ++++ b/src/evaluate.c +@@ -1106,7 +1106,7 @@ static int ct_gen_nh_dependency(struct eval_ctx *ctx, struct expr *ct) + base = pctx->protocol[PROTO_BASE_NETWORK_HDR].desc; + if (base == &proto_ip) + ct->ct.nfproto = NFPROTO_IPV4; +- else if (base == &proto_ip) ++ else if (base == &proto_ip6) + ct->ct.nfproto = NFPROTO_IPV6; + + if (base) diff --git a/0095-scanner-inet_pton-allows-for-broader-IPv4-Mapped-IPv.patch b/0095-scanner-inet_pton-allows-for-broader-IPv4-Mapped-IPv.patch new file mode 100644 index 0000000..58035a0 --- /dev/null +++ b/0095-scanner-inet_pton-allows-for-broader-IPv4-Mapped-IPv.patch @@ -0,0 +1,106 @@ +From b42e655bbc4f9068a450fadacca2e6c50bc7f13c Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:09:25 +0200 +Subject: [PATCH] scanner: inet_pton() allows for broader IPv4-Mapped IPv6 + addresses + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit fda913d712925e8a8d6460b361d49774dc5d34b8 + +commit fda913d712925e8a8d6460b361d49774dc5d34b8 +Author: Pablo Neira Ayuso +Date: Tue Jun 4 20:01:51 2024 +0200 + + scanner: inet_pton() allows for broader IPv4-Mapped IPv6 addresses + + inet_pton() allows for broader IPv4-Mapped IPv6 address syntax than + those specified by rfc4291 Sect.2.5.5. This patch extends the scanner to + support them for compatibility reasons. This allows to represent the + last 4 bytes of an IPv6 address as an IPv4 address. + + Closes: https://bugzilla.netfilter.org/show_bug.cgi?id=1730 + Fixes: fd513de78bc0 ("scanner: IPv4-Mapped IPv6 addresses support") + Fixes: 3f82ef3d0dbf ("scanner: Support rfc4291 IPv4-compatible addresses") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/scanner.l | 47 +++++++++++++++++++++++------------------------ + 1 file changed, 23 insertions(+), 24 deletions(-) + +diff --git a/src/scanner.l b/src/scanner.l +index 00a0948..28d6da3 100644 +--- a/src/scanner.l ++++ b/src/scanner.l +@@ -132,48 +132,47 @@ slash \/ + timestring ([0-9]+d)?([0-9]+h)?([0-9]+m)?([0-9]+s)?([0-9]+ms)? + + hex4 ([[:xdigit:]]{1,4}) ++rfc4291_broader (((:{hex4}){2})|(:{ip4addr})) + v680 (({hex4}:){7}{hex4}) +-v670 ((:)((:{hex4}){7})) +-v671 ((({hex4}:){1})((:{hex4}){6})) +-v672 ((({hex4}:){2})((:{hex4}){5})) +-v673 ((({hex4}:){3})((:{hex4}){4})) +-v674 ((({hex4}:){4})((:{hex4}){3})) +-v675 ((({hex4}:){5})((:{hex4}){2})) ++v670 ((:)((:{hex4}){5}){rfc4291_broader}) ++v671 ((({hex4}:){1})((:{hex4}){4}){rfc4291_broader}) ++v672 ((({hex4}:){2})((:{hex4}){3}){rfc4291_broader}) ++v673 ((({hex4}:){3})((:{hex4}){2}){rfc4291_broader}) ++v674 ((({hex4}:){4})((:{hex4}){1}){rfc4291_broader}) ++v675 ((({hex4}:){5}){rfc4291_broader}) + v676 ((({hex4}:){6})(:{hex4}{1})) + v677 ((({hex4}:){7})(:)) + v67 ({v670}|{v671}|{v672}|{v673}|{v674}|{v675}|{v676}|{v677}) +-v660 ((:)((:{hex4}){6})) +-v661 ((({hex4}:){1})((:{hex4}){5})) +-v662 ((({hex4}:){2})((:{hex4}){4})) +-v663 ((({hex4}:){3})((:{hex4}){3})) +-v664 ((({hex4}:){4})((:{hex4}){2})) ++v660 ((:)((:{hex4}){4}){rfc4291_broader}) ++v661 ((({hex4}:){1})((:{hex4}){3}){rfc4291_broader}) ++v662 ((({hex4}:){2})((:{hex4}){2}){rfc4291_broader}) ++v663 ((({hex4}:){3})((:{hex4}){1}){rfc4291_broader}) ++v664 ((({hex4}:){4}){rfc4291_broader}) + v665 ((({hex4}:){5})((:{hex4}){1})) + v666 ((({hex4}:){6})(:)) + v66 ({v660}|{v661}|{v662}|{v663}|{v664}|{v665}|{v666}) +-v650 ((:)((:{hex4}){5})) +-v651 ((({hex4}:){1})((:{hex4}){4})) +-v652 ((({hex4}:){2})((:{hex4}){3})) +-v653 ((({hex4}:){3})((:{hex4}){2})) ++v650 ((:)((:{hex4}){3}){rfc4291_broader}) ++v651 ((({hex4}:){1})((:{hex4}){2}){rfc4291_broader}) ++v652 ((({hex4}:){2})((:{hex4}){1}){rfc4291_broader}) ++v653 ((({hex4}:){3}){rfc4291_broader}) + v654 ((({hex4}:){4})(:{hex4}{1})) + v655 ((({hex4}:){5})(:)) + v65 ({v650}|{v651}|{v652}|{v653}|{v654}|{v655}) +-v640 ((:)((:{hex4}){4})) +-v641 ((({hex4}:){1})((:{hex4}){3})) +-v642 ((({hex4}:){2})((:{hex4}){2})) ++v640 ((:)((:{hex4}){2}){rfc4291_broader}) ++v641 ((({hex4}:){1})((:{hex4}){1}){rfc4291_broader}) ++v642 ((({hex4}:){2}){rfc4291_broader}) + v643 ((({hex4}:){3})((:{hex4}){1})) + v644 ((({hex4}:){4})(:)) + v64 ({v640}|{v641}|{v642}|{v643}|{v644}) +-v630 ((:)((:{hex4}){3})) +-v631 ((({hex4}:){1})((:{hex4}){2})) ++v630 ((:)((:{hex4}){1}){rfc4291_broader}) ++v631 ((({hex4}:){1}){rfc4291_broader}) + v632 ((({hex4}:){2})((:{hex4}){1})) + v633 ((({hex4}:){3})(:)) + v63 ({v630}|{v631}|{v632}|{v633}) +-v620 ((:)((:{hex4}){2})) +-v620_rfc4291 ((:)(:{ip4addr})) ++v620 ((:){rfc4291_broader}) + v621 ((({hex4}:){1})((:{hex4}){1})) + v622 ((({hex4}:){2})(:)) +-v62_rfc4291 ((:)(:[fF]{4})(:{ip4addr})) +-v62 ({v620}|{v621}|{v622}|{v62_rfc4291}|{v620_rfc4291}) ++v62 ({v620}|{v621}|{v622}) + v610 ((:)(:{hex4}{1})) + v611 ((({hex4}:){1})(:)) + v61 ({v610}|{v611}) diff --git a/0096-monitor-too-large-shift-exponent-displaying-payload-.patch b/0096-monitor-too-large-shift-exponent-displaying-payload-.patch new file mode 100644 index 0000000..f844718 --- /dev/null +++ b/0096-monitor-too-large-shift-exponent-displaying-payload-.patch @@ -0,0 +1,42 @@ +From ebe774e477b51465c58e58599a23872b82ec644c Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:09:25 +0200 +Subject: [PATCH] monitor: too large shift exponent displaying payload + expression + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 016f37f1268fa1003c46c66655697d3f58d86598 + +commit 016f37f1268fa1003c46c66655697d3f58d86598 +Author: Pablo Neira Ayuso +Date: Mon Jun 10 19:08:20 2024 +0200 + + monitor: too large shift exponent displaying payload expression + + ASAN reports too large shift exponent when displaying traces for raw + payload expression: + + trace id ec23e848 ip x y packet: oif "wlan0" src/netlink.c:2100:32: runtime error: shift exponent 1431657095 is too large for 32-bit type 'int' + + skip if proto_unknown_template is set on in this payload expression. + + Fixes: be5d9120e81e ("nft monitor [ trace ]") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/netlink.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/src/netlink.c b/src/netlink.c +index 8637186..72f4ba4 100644 +--- a/src/netlink.c ++++ b/src/netlink.c +@@ -2082,6 +2082,7 @@ restart: + /* Skip unknown and filtered expressions */ + desc = lhs->payload.desc; + if (lhs->dtype == &invalid_type || ++ lhs->payload.tmpl == &proto_unknown_template || + desc->checksum_key == payload_hdr_field(lhs) || + desc->format.filter & (1 << payload_hdr_field(lhs))) { + expr_free(lhs); diff --git a/0097-cmd-provide-better-hint-if-chain-is-already-declared.patch b/0097-cmd-provide-better-hint-if-chain-is-already-declared.patch new file mode 100644 index 0000000..816ede0 --- /dev/null +++ b/0097-cmd-provide-better-hint-if-chain-is-already-declared.patch @@ -0,0 +1,65 @@ +From c296c3d263b6bd897fd807ad174d33a3bfd2e015 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:09:25 +0200 +Subject: [PATCH] cmd: provide better hint if chain is already declared with + different type/hook/priority + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 1f321f86c45fce88a5bcd6f8eafa0157248c8b38 + +commit 1f321f86c45fce88a5bcd6f8eafa0157248c8b38 +Author: Pablo Neira Ayuso +Date: Mon Jun 10 19:36:21 2024 +0200 + + cmd: provide better hint if chain is already declared with different type/hook/priority + + Display the following error in such case: + + ruleset.nft:7:9-52: Error: Chain "input" already exists in table ip 'filter' with different declaration + type filter hook postrouting priority filter; + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + instead of reporting a misleading unsupported chain type when updating + an existing chain with different type/hook/priority. + + Fixes: 573788e05363 ("src: improve error reporting for unsupported chain type") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/cmd.c | 15 ++++++++++++++- + 1 file changed, 14 insertions(+), 1 deletion(-) + +diff --git a/src/cmd.c b/src/cmd.c +index 21533e0..de11468 100644 +--- a/src/cmd.c ++++ b/src/cmd.c +@@ -256,7 +256,8 @@ static void nft_cmd_enoent(struct netlink_ctx *ctx, const struct cmd *cmd, + static int nft_cmd_chain_error(struct netlink_ctx *ctx, struct cmd *cmd, + struct mnl_err *err) + { +- struct chain *chain = cmd->chain; ++ struct chain *chain = cmd->chain, *existing_chain; ++ const struct table *table; + int priority; + + switch (err->err) { +@@ -270,6 +271,18 @@ static int nft_cmd_chain_error(struct netlink_ctx *ctx, struct cmd *cmd, + return netlink_io_error(ctx, &chain->priority.loc, + "Chains of type \"nat\" must have a priority value above -200"); + ++ table = table_cache_find(&ctx->nft->cache.table_cache, ++ cmd->handle.table.name, cmd->handle.family); ++ if (table) { ++ existing_chain = chain_cache_find(table, cmd->handle.chain.name); ++ if (existing_chain && existing_chain != chain && ++ !strcmp(existing_chain->handle.chain.name, chain->handle.chain.name)) ++ return netlink_io_error(ctx, &chain->loc, ++ "Chain \"%s\" already exists in table %s '%s' with different declaration", ++ chain->handle.chain.name, ++ family2str(table->handle.family), table->handle.table.name); ++ } ++ + return netlink_io_error(ctx, &chain->loc, + "Chain of type \"%s\" is not supported, perhaps kernel support is missing?", + chain->type.str); diff --git a/0098-cmd-skip-variable-set-elements-when-collapsing-comma.patch b/0098-cmd-skip-variable-set-elements-when-collapsing-comma.patch new file mode 100644 index 0000000..245d982 --- /dev/null +++ b/0098-cmd-skip-variable-set-elements-when-collapsing-comma.patch @@ -0,0 +1,88 @@ +From 5544e2dd7b3f219d54b06dfc779bdff4646ac420 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:09:25 +0200 +Subject: [PATCH] cmd: skip variable set elements when collapsing commands + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit be055af5c58d9a4751990684d8f83b85082ca443 +Conflicts: Dropped changes to non-existent .json-nft dump + +commit be055af5c58d9a4751990684d8f83b85082ca443 +Author: Pablo Neira Ayuso +Date: Tue Jun 11 17:40:23 2024 +0200 + + cmd: skip variable set elements when collapsing commands + + ASAN reports an issue when collapsing commands that represent an element + through a variable: + + include/list.h:60:13: runtime error: member access within null pointer of type 'struct list_head' + AddressSanitizer:DEADLYSIGNAL + ================================================================= + ==11398==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000 (pc 0x7ffb77cf09c2 bp 0x7ffc818267c0 sp 0x7ffc818267a0 T0) + ==11398==The signal is caused by a WRITE memory access. + ==11398==Hint: address points to the zero page. + #0 0x7ffb77cf09c2 in __list_add include/list.h:60 + #1 0x7ffb77cf0ad9 in list_add_tail include/list.h:87 + #2 0x7ffb77cf0e72 in list_move_tail include/list.h:169 + #3 0x7ffb77cf86ad in nft_cmd_collapse src/cmd.c:478 + #4 0x7ffb77da9f16 in nft_evaluate src/libnftables.c:531 + #5 0x7ffb77dac471 in __nft_run_cmd_from_filename src/libnftables.c:720 + #6 0x7ffb77dad703 in nft_run_cmd_from_filename src/libnftables.c:807 + + Skip such commands to address this issue. + + This patch also extends tests/shell to cover for this bug. + + Closes: https://bugzilla.netfilter.org/show_bug.cgi?id=1754 + Fixes: 498a5f0c219d ("rule: collapse set element commands") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/cmd.c | 3 +++ + tests/shell/testcases/sets/collapse_elem_0 | 6 ++++++ + tests/shell/testcases/sets/dumps/collapse_elem_0.nft | 2 +- + 3 files changed, 10 insertions(+), 1 deletion(-) + +diff --git a/src/cmd.c b/src/cmd.c +index de11468..8f465de 100644 +--- a/src/cmd.c ++++ b/src/cmd.c +@@ -455,6 +455,9 @@ bool nft_cmd_collapse(struct list_head *cmds) + continue; + } + ++ if (cmd->expr->etype == EXPR_VARIABLE) ++ continue; ++ + if (!elems) { + elems = cmd; + continue; +diff --git a/tests/shell/testcases/sets/collapse_elem_0 b/tests/shell/testcases/sets/collapse_elem_0 +index 7699e9d..52a42c2 100755 +--- a/tests/shell/testcases/sets/collapse_elem_0 ++++ b/tests/shell/testcases/sets/collapse_elem_0 +@@ -17,3 +17,9 @@ add element ip a x { 2 } + add element ip6 a x { 2 }" + + $NFT -f - <<< $RULESET ++ ++RULESET="define m = { 3, 4 } ++add element ip a x \$m ++add element ip a x { 5 }" ++ ++$NFT -f - <<< $RULESET +diff --git a/tests/shell/testcases/sets/dumps/collapse_elem_0.nft b/tests/shell/testcases/sets/dumps/collapse_elem_0.nft +index a3244fc..775f0ab 100644 +--- a/tests/shell/testcases/sets/dumps/collapse_elem_0.nft ++++ b/tests/shell/testcases/sets/dumps/collapse_elem_0.nft +@@ -1,7 +1,7 @@ + table ip a { + set x { + type inet_service +- elements = { 1, 2 } ++ elements = { 1, 2, 3, 4, 5 } + } + } + table ip6 a { diff --git a/0099-tests-shell-skip-ip-option-tests-if-kernel-does-not-.patch b/0099-tests-shell-skip-ip-option-tests-if-kernel-does-not-.patch new file mode 100644 index 0000000..5fe1173 --- /dev/null +++ b/0099-tests-shell-skip-ip-option-tests-if-kernel-does-not-.patch @@ -0,0 +1,51 @@ +From 09ae9247749aa7768655696fd099561322b1a90e Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:09:42 +0200 +Subject: [PATCH] tests: shell: skip ip option tests if kernel does not support + it + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit c624578ee18131492e7f72b662d5faf7c042e0d8 + +commit c624578ee18131492e7f72b662d5faf7c042e0d8 +Author: Pablo Neira Ayuso +Date: Thu Jun 13 00:37:48 2024 +0200 + + tests: shell: skip ip option tests if kernel does not support it + + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + tests/shell/features/ip_options.nft | 8 ++++++++ + tests/shell/testcases/sets/typeof_sets_0 | 2 ++ + 2 files changed, 10 insertions(+) + create mode 100644 tests/shell/features/ip_options.nft + +diff --git a/tests/shell/features/ip_options.nft b/tests/shell/features/ip_options.nft +new file mode 100644 +index 0000000..0b8cb09 +--- /dev/null ++++ b/tests/shell/features/ip_options.nft +@@ -0,0 +1,8 @@ ++# dbb5281a1f84 ("netfilter: nf_tables: add support for matching IPv4 options") ++# v5.3-rc1~140^2~153^2~1 ++ ++table ip x { ++ chain y { ++ ip option ra value 255 ++ } ++} +diff --git a/tests/shell/testcases/sets/typeof_sets_0 b/tests/shell/testcases/sets/typeof_sets_0 +index 943c9c2..3a4ed94 100755 +--- a/tests/shell/testcases/sets/typeof_sets_0 ++++ b/tests/shell/testcases/sets/typeof_sets_0 +@@ -4,6 +4,8 @@ + # s1 and s2 are identical, they just use different + # ways for declaration. + ++# NFT_TEST_REQUIRES(NFT_TEST_HAVE_ip_options) ++ + set -e + + die() { diff --git a/0100-src-add-string-preprocessor-and-use-it-for-log-prefi.patch b/0100-src-add-string-preprocessor-and-use-it-for-log-prefi.patch new file mode 100644 index 0000000..a825535 --- /dev/null +++ b/0100-src-add-string-preprocessor-and-use-it-for-log-prefi.patch @@ -0,0 +1,607 @@ +From cf8cedf79d49736dad61f01628a0cf738fefad26 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:09:42 +0200 +Subject: [PATCH] src: add string preprocessor and use it for log prefix string + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 081bf5f0d7952a6e6ac0d23a365ccf1fd27010c0 +Conflicts: Manually applied Makefile.am change due to missing commit + 11e62138424ad ("build: no recursive make for "src/Makefile.am"") + +commit 081bf5f0d7952a6e6ac0d23a365ccf1fd27010c0 +Author: Pablo Neira Ayuso +Date: Tue Jun 18 14:26:31 2024 +0200 + + src: add string preprocessor and use it for log prefix string + + Add a string preprocessor to identify and replace variables in a string. + Rework existing support to variables in log prefix strings to use it. + + Fixes: e76bb3794018 ("src: allow for variables in the log prefix string") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + include/expression.h | 2 - + include/parser.h | 4 + + include/statement.h | 2 +- + src/Makefile.am | 1 + + src/evaluate.c | 45 +--------- + src/expression.c | 9 -- + src/json.c | 7 +- + src/netlink_delinearize.c | 6 +- + src/netlink_linearize.c | 7 +- + src/optimize.c | 6 +- + src/parser_bison.y | 126 ++-------------------------- + src/parser_json.c | 4 +- + src/preprocess.c | 168 ++++++++++++++++++++++++++++++++++++++ + src/statement.c | 10 +-- + 14 files changed, 198 insertions(+), 199 deletions(-) + create mode 100644 src/preprocess.c + +diff --git a/include/expression.h b/include/expression.h +index 809089c..e143e71 100644 +--- a/include/expression.h ++++ b/include/expression.h +@@ -414,8 +414,6 @@ extern const struct datatype *expr_basetype(const struct expr *expr); + extern void expr_set_type(struct expr *expr, const struct datatype *dtype, + enum byteorder byteorder); + +-void expr_to_string(const struct expr *expr, char *string); +- + struct eval_ctx; + extern int expr_binary_error(struct list_head *msgs, + const struct expr *e1, const struct expr *e2, +diff --git a/include/parser.h b/include/parser.h +index f79a22f..576e5e4 100644 +--- a/include/parser.h ++++ b/include/parser.h +@@ -112,4 +112,8 @@ extern void scanner_push_buffer(void *scanner, + + extern void scanner_pop_start_cond(void *scanner, enum startcond_type sc); + ++const char *str_preprocess(struct parser_state *state, struct location *loc, ++ struct scope *scope, const char *x, ++ struct error_record **rec); ++ + #endif /* NFTABLES_PARSER_H */ +diff --git a/include/statement.h b/include/statement.h +index 662f99d..9376911 100644 +--- a/include/statement.h ++++ b/include/statement.h +@@ -90,7 +90,7 @@ enum { + }; + + struct log_stmt { +- struct expr *prefix; ++ const char *prefix; + unsigned int snaplen; + uint16_t group; + uint16_t qthreshold; +diff --git a/src/Makefile.am b/src/Makefile.am +index 63a4ef4..6d7bb89 100644 +--- a/src/Makefile.am ++++ b/src/Makefile.am +@@ -75,6 +75,7 @@ libnftables_la_SOURCES = \ + nfnl_osf.c \ + tcpopt.c \ + socket.c \ ++ preprocess.c \ + print.c \ + sctp_chunk.c \ + dccpopt.c \ +diff --git a/src/evaluate.c b/src/evaluate.c +index 7f5b64b..c28f693 100644 +--- a/src/evaluate.c ++++ b/src/evaluate.c +@@ -4292,49 +4292,12 @@ static int stmt_evaluate_queue(struct eval_ctx *ctx, struct stmt *stmt) + + static int stmt_evaluate_log_prefix(struct eval_ctx *ctx, struct stmt *stmt) + { +- char tmp[NF_LOG_PREFIXLEN] = {}; +- char prefix[NF_LOG_PREFIXLEN]; +- size_t len = sizeof(prefix); +- size_t offset = 0; +- struct expr *expr; +- +- if (stmt->log.prefix->etype != EXPR_LIST) { +- if (stmt->log.prefix && +- div_round_up(stmt->log.prefix->len, BITS_PER_BYTE) >= NF_LOG_PREFIXLEN) +- return expr_error(ctx->msgs, stmt->log.prefix, "log prefix is too long"); +- +- return 0; +- } +- +- prefix[0] = '\0'; +- +- list_for_each_entry(expr, &stmt->log.prefix->expressions, list) { +- int ret; +- +- switch (expr->etype) { +- case EXPR_VALUE: +- expr_to_string(expr, tmp); +- ret = snprintf(prefix + offset, len, "%s", tmp); +- break; +- case EXPR_VARIABLE: +- ret = snprintf(prefix + offset, len, "%s", +- expr->sym->expr->identifier); +- break; +- default: +- BUG("unknown expression type %s\n", expr_name(expr)); +- break; +- } +- SNPRINTF_BUFFER_SIZE(ret, &len, &offset); +- } ++ unsigned int len = strlen(stmt->log.prefix); + +- if (len == 0) ++ if (len >= NF_LOG_PREFIXLEN) + return stmt_error(ctx, stmt, "log prefix is too long"); +- +- expr = constant_expr_alloc(&stmt->log.prefix->location, &string_type, +- BYTEORDER_HOST_ENDIAN, +- strlen(prefix) * BITS_PER_BYTE, prefix); +- expr_free(stmt->log.prefix); +- stmt->log.prefix = expr; ++ else if (len == 0) ++ return stmt_error(ctx, stmt, "log prefix must have a minimum length of 1 character"); + + return 0; + } +diff --git a/src/expression.c b/src/expression.c +index cb2573f..992f510 100644 +--- a/src/expression.c ++++ b/src/expression.c +@@ -183,15 +183,6 @@ void expr_describe(const struct expr *expr, struct output_ctx *octx) + } + } + +-void expr_to_string(const struct expr *expr, char *string) +-{ +- int len = expr->len / BITS_PER_BYTE; +- +- assert(expr->dtype == &string_type); +- +- mpz_export_data(string, expr->value, BYTEORDER_HOST_ENDIAN, len); +-} +- + void expr_set_type(struct expr *expr, const struct datatype *dtype, + enum byteorder byteorder) + { +diff --git a/src/json.c b/src/json.c +index b3e1e4e..5faeb4d 100644 +--- a/src/json.c ++++ b/src/json.c +@@ -1319,12 +1319,9 @@ json_t *log_stmt_json(const struct stmt *stmt, struct output_ctx *octx) + { + json_t *root = json_object(), *flags; + +- if (stmt->log.flags & STMT_LOG_PREFIX) { +- char prefix[NF_LOG_PREFIXLEN] = {}; ++ if (stmt->log.flags & STMT_LOG_PREFIX) ++ json_object_set_new(root, "prefix", json_string(stmt->log.prefix)); + +- expr_to_string(stmt->log.prefix, prefix); +- json_object_set_new(root, "prefix", json_string(prefix)); +- } + if (stmt->log.flags & STMT_LOG_GROUP) + json_object_set_new(root, "group", + json_integer(stmt->log.group)); +diff --git a/src/netlink_delinearize.c b/src/netlink_delinearize.c +index 1af0278..0ea10ee 100644 +--- a/src/netlink_delinearize.c ++++ b/src/netlink_delinearize.c +@@ -1090,11 +1090,7 @@ static void netlink_parse_log(struct netlink_parse_ctx *ctx, + stmt = log_stmt_alloc(loc); + prefix = nftnl_expr_get_str(nle, NFTNL_EXPR_LOG_PREFIX); + if (nftnl_expr_is_set(nle, NFTNL_EXPR_LOG_PREFIX)) { +- stmt->log.prefix = constant_expr_alloc(&internal_location, +- &string_type, +- BYTEORDER_HOST_ENDIAN, +- (strlen(prefix) + 1) * BITS_PER_BYTE, +- prefix); ++ stmt->log.prefix = xstrdup(prefix); + stmt->log.flags |= STMT_LOG_PREFIX; + } + if (nftnl_expr_is_set(nle, NFTNL_EXPR_LOG_GROUP)) { +diff --git a/src/netlink_linearize.c b/src/netlink_linearize.c +index df395ba..a2a1cd5 100644 +--- a/src/netlink_linearize.c ++++ b/src/netlink_linearize.c +@@ -1141,12 +1141,9 @@ static void netlink_gen_log_stmt(struct netlink_linearize_ctx *ctx, + struct nftnl_expr *nle; + + nle = alloc_nft_expr("log"); +- if (stmt->log.prefix != NULL) { +- char prefix[NF_LOG_PREFIXLEN] = {}; ++ if (stmt->log.prefix != NULL) ++ nftnl_expr_set_str(nle, NFTNL_EXPR_LOG_PREFIX, stmt->log.prefix); + +- expr_to_string(stmt->log.prefix, prefix); +- nftnl_expr_set_str(nle, NFTNL_EXPR_LOG_PREFIX, prefix); +- } + if (stmt->log.flags & STMT_LOG_GROUP) { + nftnl_expr_set_u16(nle, NFTNL_EXPR_LOG_GROUP, stmt->log.group); + if (stmt->log.flags & STMT_LOG_SNAPLEN) +diff --git a/src/optimize.c b/src/optimize.c +index b90dd99..1dd0858 100644 +--- a/src/optimize.c ++++ b/src/optimize.c +@@ -215,9 +215,7 @@ static bool __stmt_type_eq(const struct stmt *stmt_a, const struct stmt *stmt_b, + if (!stmt_a->log.prefix) + return true; + +- if (stmt_a->log.prefix->etype != EXPR_VALUE || +- stmt_b->log.prefix->etype != EXPR_VALUE || +- mpz_cmp(stmt_a->log.prefix->value, stmt_b->log.prefix->value)) ++ if (strcmp(stmt_a->log.prefix, stmt_b->log.prefix)) + return false; + break; + case STMT_REJECT: +@@ -406,7 +404,7 @@ static int rule_collect_stmts(struct optimize_ctx *ctx, struct rule *rule) + case STMT_LOG: + memcpy(&clone->log, &stmt->log, sizeof(clone->log)); + if (stmt->log.prefix) +- clone->log.prefix = expr_get(stmt->log.prefix); ++ clone->log.prefix = xstrdup(stmt->log.prefix); + break; + case STMT_NAT: + if ((stmt->nat.addr && +diff --git a/src/parser_bison.y b/src/parser_bison.y +index e3ac2fb..678dd50 100644 +--- a/src/parser_bison.y ++++ b/src/parser_bison.y +@@ -3338,127 +3338,19 @@ log_args : log_arg + log_arg : PREFIX string + { + struct scope *scope = current_scope(state); +- bool done = false, another_var = false; +- char *start, *end, scratch = '\0'; +- struct expr *expr, *item; +- struct symbol *sym; +- enum { +- PARSE_TEXT, +- PARSE_VAR, +- } prefix_state; +- +- /* No variables in log prefix, skip. */ +- if (!strchr($2, '$')) { +- expr = constant_expr_alloc(&@$, &string_type, +- BYTEORDER_HOST_ENDIAN, +- (strlen($2) + 1) * BITS_PER_BYTE, $2); +- free_const($2); +- $0->log.prefix = expr; +- $0->log.flags |= STMT_LOG_PREFIX; +- break; +- } +- +- /* Parse variables in log prefix string using a +- * state machine parser with two states. This +- * parser creates list of expressions composed +- * of constant and variable expressions. +- */ +- expr = compound_expr_alloc(&@$, EXPR_LIST); +- +- start = (char *)$2; ++ struct error_record *erec; ++ const char *prefix; + +- if (*start != '$') { +- prefix_state = PARSE_TEXT; +- } else { +- prefix_state = PARSE_VAR; +- start++; +- } +- end = start; +- +- /* Not nice, but works. */ +- while (!done) { +- switch (prefix_state) { +- case PARSE_TEXT: +- while (*end != '\0' && *end != '$') +- end++; +- +- if (*end == '\0') +- done = true; +- +- *end = '\0'; +- item = constant_expr_alloc(&@$, &string_type, +- BYTEORDER_HOST_ENDIAN, +- (strlen(start) + 1) * BITS_PER_BYTE, +- start); +- compound_expr_add(expr, item); +- +- if (done) +- break; +- +- start = end + 1; +- end = start; +- +- /* fall through */ +- case PARSE_VAR: +- while (isalnum(*end) || *end == '_') +- end++; +- +- if (*end == '\0') +- done = true; +- else if (*end == '$') +- another_var = true; +- else +- scratch = *end; +- +- *end = '\0'; +- +- sym = symbol_get(scope, start); +- if (!sym) { +- sym = symbol_lookup_fuzzy(scope, start); +- if (sym) { +- erec_queue(error(&@2, "unknown identifier '%s'; " +- "did you mean identifier ‘%s’?", +- start, sym->identifier), +- state->msgs); +- } else { +- erec_queue(error(&@2, "unknown identifier '%s'", +- start), +- state->msgs); +- } +- expr_free(expr); +- free_const($2); +- YYERROR; +- } +- item = variable_expr_alloc(&@$, scope, sym); +- compound_expr_add(expr, item); +- +- if (done) +- break; +- +- /* Restore original byte after +- * symbol lookup. +- */ +- if (scratch) { +- *end = scratch; +- scratch = '\0'; +- } +- +- start = end; +- if (another_var) { +- another_var = false; +- start++; +- prefix_state = PARSE_VAR; +- } else { +- prefix_state = PARSE_TEXT; +- } +- end = start; +- break; +- } ++ prefix = str_preprocess(state, &@2, scope, $2, &erec); ++ if (!prefix) { ++ erec_queue(erec, state->msgs); ++ free_const($2); ++ YYERROR; + } + + free_const($2); +- $0->log.prefix = expr; +- $0->log.flags |= STMT_LOG_PREFIX; ++ $0->log.prefix = prefix; ++ $0->log.flags |= STMT_LOG_PREFIX; + } + | GROUP NUM + { +diff --git a/src/parser_json.c b/src/parser_json.c +index 25483b1..ce36397 100644 +--- a/src/parser_json.c ++++ b/src/parser_json.c +@@ -2540,9 +2540,7 @@ static struct stmt *json_parse_log_stmt(struct json_ctx *ctx, + stmt = log_stmt_alloc(int_loc); + + if (!json_unpack(value, "{s:s}", "prefix", &tmpstr)) { +- stmt->log.prefix = constant_expr_alloc(int_loc, &string_type, +- BYTEORDER_HOST_ENDIAN, +- (strlen(tmpstr) + 1) * BITS_PER_BYTE, tmpstr); ++ stmt->log.prefix = xstrdup(tmpstr); + stmt->log.flags |= STMT_LOG_PREFIX; + } + if (!json_unpack(value, "{s:i}", "group", &tmp)) { +diff --git a/src/preprocess.c b/src/preprocess.c +new file mode 100644 +index 0000000..619f67a +--- /dev/null ++++ b/src/preprocess.c +@@ -0,0 +1,168 @@ ++/* ++ * Copyright (c) 2013-2024 Pablo Neira Ayuso ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 (or any ++ * later) as published by the Free Software Foundation. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "list.h" ++#include "parser.h" ++#include "erec.h" ++ ++struct str_buf { ++ uint8_t *str; ++ uint32_t len; ++ uint32_t size; ++}; ++ ++#define STR_BUF_LEN 128 ++ ++static struct str_buf *str_buf_alloc(void) ++{ ++ struct str_buf *buf; ++ ++ buf = xzalloc(sizeof(*buf)); ++ buf->str = xzalloc_array(1, STR_BUF_LEN); ++ buf->size = STR_BUF_LEN; ++ ++ return buf; ++} ++ ++static int str_buf_add(struct str_buf *buf, const char *str, uint32_t len) ++{ ++ uint8_t *tmp; ++ ++ if (len + buf->len > buf->size) { ++ buf->size = (len + buf->len) * 2; ++ tmp = xrealloc(buf->str, buf->size); ++ buf->str = tmp; ++ } ++ ++ memcpy(&buf->str[buf->len], str, len); ++ buf->len += len; ++ ++ return 0; ++} ++ ++struct str_chunk { ++ struct list_head list; ++ char *str; ++ uint32_t len; ++ bool is_sym; ++}; ++ ++static void add_str_chunk(const char *x, int from, int to, struct list_head *list, bool is_sym) ++{ ++ struct str_chunk *chunk; ++ int len = to - from; ++ ++ chunk = xzalloc_array(1, sizeof(*chunk)); ++ chunk->str = xzalloc_array(1, len + 1); ++ chunk->is_sym = is_sym; ++ chunk->len = len; ++ memcpy(chunk->str, &x[from], len); ++ ++ list_add_tail(&chunk->list, list); ++} ++ ++static void free_str_chunk(struct str_chunk *chunk) ++{ ++ free(chunk->str); ++ free(chunk); ++} ++ ++const char *str_preprocess(struct parser_state *state, struct location *loc, ++ struct scope *scope, const char *x, ++ struct error_record **erec) ++{ ++ struct str_chunk *chunk, *next; ++ struct str_buf *buf; ++ const char *str; ++ int i, j, start; ++ LIST_HEAD(list); ++ ++ start = 0; ++ i = 0; ++ while (1) { ++ if (x[i] == '\0') { ++ i++; ++ break; ++ } ++ ++ if (x[i] != '$') { ++ i++; ++ continue; ++ } ++ ++ if (isdigit(x[++i])) ++ continue; ++ ++ j = i; ++ while (1) { ++ if (isalpha(x[i]) || ++ isdigit(x[i]) || ++ x[i] == '_') { ++ i++; ++ continue; ++ } ++ break; ++ } ++ add_str_chunk(x, start, j-1, &list, false); ++ add_str_chunk(x, j, i, &list, true); ++ start = i; ++ } ++ if (start != i) ++ add_str_chunk(x, start, i, &list, false); ++ ++ buf = str_buf_alloc(); ++ ++ list_for_each_entry_safe(chunk, next, &list, list) { ++ if (chunk->is_sym) { ++ struct symbol *sym; ++ ++ sym = symbol_lookup(scope, chunk->str); ++ if (!sym) { ++ sym = symbol_lookup_fuzzy(scope, chunk->str); ++ if (sym) { ++ *erec = error(loc, "unknown identifier '%s'; " ++ "did you mean identifier '%s'?", ++ chunk->str, sym->identifier); ++ } else { ++ *erec = error(loc, "unknown identifier '%s'", ++ chunk->str); ++ } ++ goto err; ++ } ++ str_buf_add(buf, sym->expr->identifier, ++ strlen(sym->expr->identifier)); ++ } else { ++ str_buf_add(buf, chunk->str, chunk->len); ++ } ++ list_del(&chunk->list); ++ free_str_chunk(chunk); ++ } ++ ++ str = (char *)buf->str; ++ ++ free(buf); ++ ++ return (char *)str; ++err: ++ list_for_each_entry_safe(chunk, next, &list, list) { ++ list_del(&chunk->list); ++ free_str_chunk(chunk); ++ } ++ free(buf->str); ++ free(buf); ++ ++ return NULL; ++} +diff --git a/src/statement.c b/src/statement.c +index ab144d6..551cd13 100644 +--- a/src/statement.c ++++ b/src/statement.c +@@ -377,12 +377,8 @@ int log_level_parse(const char *level) + static void log_stmt_print(const struct stmt *stmt, struct output_ctx *octx) + { + nft_print(octx, "log"); +- if (stmt->log.flags & STMT_LOG_PREFIX) { +- char prefix[NF_LOG_PREFIXLEN] = {}; +- +- expr_to_string(stmt->log.prefix, prefix); +- nft_print(octx, " prefix \"%s\"", prefix); +- } ++ if (stmt->log.flags & STMT_LOG_PREFIX) ++ nft_print(octx, " prefix \"%s\"", stmt->log.prefix); + if (stmt->log.flags & STMT_LOG_GROUP) + nft_print(octx, " group %u", stmt->log.group); + if (stmt->log.flags & STMT_LOG_SNAPLEN) +@@ -419,7 +415,7 @@ static void log_stmt_print(const struct stmt *stmt, struct output_ctx *octx) + + static void log_stmt_destroy(struct stmt *stmt) + { +- expr_free(stmt->log.prefix); ++ free_const(stmt->log.prefix); + } + + static const struct stmt_ops log_stmt_ops = { diff --git a/0101-intervals-fix-element-deletions-with-maps.patch b/0101-intervals-fix-element-deletions-with-maps.patch new file mode 100644 index 0000000..e754431 --- /dev/null +++ b/0101-intervals-fix-element-deletions-with-maps.patch @@ -0,0 +1,110 @@ +From bbfd033cb9014f854f6a4db3cec2aafe160847d4 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:10:08 +0200 +Subject: [PATCH] intervals: fix element deletions with maps + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 551a4ad68b922fa6c942f5e79ac59f723a12e233 + +commit 551a4ad68b922fa6c942f5e79ac59f723a12e233 +Author: Pablo Neira Ayuso +Date: Wed Jul 3 16:29:26 2024 +0200 + + intervals: fix element deletions with maps + + Set element deletion in maps (including catchall elements) does not work. + + # nft delete element ip x m { \* } + BUG: invalid range expression type catch-all set element + nft: src/expression.c:1472: range_expr_value_low: Assertion `0' failed. + Aborted + + Call interval_expr_key() to fetch expr->left in the mapping but use the + expression that represents the mapping because it provides access to the + EXPR_F_REMOVE flags. + + Moreover, assume maximum value for catchall expression by means of the + expr->len to reuse the existing code to check if the element to be + deleted really exists. + + Fixes: 3e8d934e4f72 ("intervals: support to partial deletion with automerge") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/intervals.c | 31 ++++++++++++++++++------------- + 1 file changed, 18 insertions(+), 13 deletions(-) + +diff --git a/src/intervals.c b/src/intervals.c +index d5406ef..af1415a 100644 +--- a/src/intervals.c ++++ b/src/intervals.c +@@ -378,7 +378,7 @@ static int setelem_delete(struct list_head *msgs, struct set *set, + struct expr *purge, struct expr *elems, + unsigned int debug_mask) + { +- struct expr *i, *next, *prev = NULL; ++ struct expr *i, *next, *elem, *prev = NULL; + struct range range, prev_range; + int err = 0; + mpz_t rop; +@@ -389,21 +389,26 @@ static int setelem_delete(struct list_head *msgs, struct set *set, + mpz_init(range.high); + mpz_init(rop); + +- list_for_each_entry_safe(i, next, &elems->expressions, list) { +- if (i->key->etype == EXPR_SET_ELEM_CATCHALL) +- continue; ++ list_for_each_entry_safe(elem, next, &elems->expressions, list) { ++ i = interval_expr_key(elem); + +- range_expr_value_low(range.low, i); +- range_expr_value_high(range.high, i); ++ if (i->key->etype == EXPR_SET_ELEM_CATCHALL) { ++ /* Assume max value to simplify handling. */ ++ mpz_bitmask(range.low, i->len); ++ mpz_bitmask(range.high, i->len); ++ } else { ++ range_expr_value_low(range.low, i); ++ range_expr_value_high(range.high, i); ++ } + +- if (!prev && i->flags & EXPR_F_REMOVE) { ++ if (!prev && elem->flags & EXPR_F_REMOVE) { + expr_error(msgs, i, "element does not exist"); + err = -1; + goto err; + } + +- if (!(i->flags & EXPR_F_REMOVE)) { +- prev = i; ++ if (!(elem->flags & EXPR_F_REMOVE)) { ++ prev = elem; + mpz_set(prev_range.low, range.low); + mpz_set(prev_range.high, range.high); + continue; +@@ -411,12 +416,12 @@ static int setelem_delete(struct list_head *msgs, struct set *set, + + if (mpz_cmp(prev_range.low, range.low) == 0 && + mpz_cmp(prev_range.high, range.high) == 0) { +- if (i->flags & EXPR_F_REMOVE) { ++ if (elem->flags & EXPR_F_REMOVE) { + if (prev->flags & EXPR_F_KERNEL) + list_move_tail(&prev->list, &purge->expressions); + +- list_del(&i->list); +- expr_free(i); ++ list_del(&elem->list); ++ expr_free(elem); + } + } else if (set->automerge) { + if (setelem_adjust(set, purge, &prev_range, &range, prev, i) < 0) { +@@ -424,7 +429,7 @@ static int setelem_delete(struct list_head *msgs, struct set *set, + err = -1; + goto err; + } +- } else if (i->flags & EXPR_F_REMOVE) { ++ } else if (elem->flags & EXPR_F_REMOVE) { + expr_error(msgs, i, "element does not exist"); + err = -1; + goto err; diff --git a/0102-parser_bison-recursive-table-declaration-in-deprecat.patch b/0102-parser_bison-recursive-table-declaration-in-deprecat.patch new file mode 100644 index 0000000..0e08951 --- /dev/null +++ b/0102-parser_bison-recursive-table-declaration-in-deprecat.patch @@ -0,0 +1,68 @@ +From 765603a3236f4d50de140148bd2a0a950c334aab Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:10:08 +0200 +Subject: [PATCH] parser_bison: recursive table declaration in deprecated meter + statement + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit a70a217079ef83482fc093d8549f8cdeaeaa3cae + +commit a70a217079ef83482fc093d8549f8cdeaeaa3cae +Author: Pablo Neira Ayuso +Date: Wed Jul 3 00:08:01 2024 +0200 + + parser_bison: recursive table declaration in deprecated meter statement + + This is allowing for recursive table NAME declarations such as: + + ... table xyz1 table xyz2 { ... } + + remove it. + + Fixes: 3ed5e31f4a32 ("src: add flow statement") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/parser_bison.y | 20 ++++---------------- + 1 file changed, 4 insertions(+), 16 deletions(-) + +diff --git a/src/parser_bison.y b/src/parser_bison.y +index 678dd50..87cf9ae 100644 +--- a/src/parser_bison.y ++++ b/src/parser_bison.y +@@ -4178,10 +4178,11 @@ map_stmt : set_stmt_op set_ref_expr '{' set_elem_expr_stmt COLON set_elem_expr_ + } + ; + +-meter_stmt : flow_stmt_legacy_alloc flow_stmt_opts '{' meter_key_expr stmt '}' ++meter_stmt : flow_stmt_legacy_alloc TABLE identifier '{' meter_key_expr stmt '}' + { +- $1->meter.key = $4; +- $1->meter.stmt = $5; ++ $1->meter.name = $3; ++ $1->meter.key = $5; ++ $1->meter.stmt = $6; + $$->location = @$; + $$ = $1; + } +@@ -4194,19 +4195,6 @@ flow_stmt_legacy_alloc : FLOW + } + ; + +-flow_stmt_opts : flow_stmt_opt +- { +- $$ = $0; +- } +- | flow_stmt_opts flow_stmt_opt +- ; +- +-flow_stmt_opt : TABLE identifier +- { +- $0->meter.name = $2; +- } +- ; +- + meter_stmt_alloc : METER identifier '{' meter_key_expr stmt '}' + { + $$ = meter_stmt_alloc(&@$); diff --git a/0103-evaluate-set-on-expr-len-for-catchall-set-elements.patch b/0103-evaluate-set-on-expr-len-for-catchall-set-elements.patch new file mode 100644 index 0000000..843aace --- /dev/null +++ b/0103-evaluate-set-on-expr-len-for-catchall-set-elements.patch @@ -0,0 +1,59 @@ +From e9372be1f7b089f58c49d3093f0973c2bf5bfc9a Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:10:08 +0200 +Subject: [PATCH] evaluate: set on expr->len for catchall set elements + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit b523008535f3de78ed5834a302ba07cda4b4c8fd + +commit b523008535f3de78ed5834a302ba07cda4b4c8fd +Author: Pablo Neira Ayuso +Date: Thu Jul 4 16:38:22 2024 +0200 + + evaluate: set on expr->len for catchall set elements + + Catchall elements coming from the parser provide expr->len == 0. + However, the existing mergesort implementation requires expr->len to be + set up to the length of the set key to properly sort elements. + + In particular, set element deletion leverages such list sorting to find + if elements exists in the set. + + Fixes: 419d19688688 ("src: add set element catch-all support") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/evaluate.c | 12 +++++++++++- + 1 file changed, 11 insertions(+), 1 deletion(-) + +diff --git a/src/evaluate.c b/src/evaluate.c +index c28f693..af811a7 100644 +--- a/src/evaluate.c ++++ b/src/evaluate.c +@@ -1802,6 +1802,16 @@ err_missing_flag: + set_is_map(ctx->set->flags) ? "map" : "set", expr_name(key)); + } + ++static int expr_evaluate_set_elem_catchall(struct eval_ctx *ctx, struct expr **expr) ++{ ++ struct expr *elem = *expr; ++ ++ if (ctx->set) ++ elem->len = ctx->set->key->len; ++ ++ return 0; ++} ++ + static const struct expr *expr_set_elem(const struct expr *expr) + { + if (expr->etype == EXPR_MAPPING) +@@ -2866,7 +2876,7 @@ static int expr_evaluate(struct eval_ctx *ctx, struct expr **expr) + case EXPR_XFRM: + return expr_evaluate_xfrm(ctx, expr); + case EXPR_SET_ELEM_CATCHALL: +- return 0; ++ return expr_evaluate_set_elem_catchall(ctx, expr); + case EXPR_FLAGCMP: + return expr_evaluate_flagcmp(ctx, expr); + default: diff --git a/0104-segtree-set-on-EXPR_F_KERNEL-flag-for-catchall-eleme.patch b/0104-segtree-set-on-EXPR_F_KERNEL-flag-for-catchall-eleme.patch new file mode 100644 index 0000000..5808e6c --- /dev/null +++ b/0104-segtree-set-on-EXPR_F_KERNEL-flag-for-catchall-eleme.patch @@ -0,0 +1,43 @@ +From a49ad23fb841de5c9c7debd135eb3cb8fe47d444 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:10:08 +0200 +Subject: [PATCH] segtree: set on EXPR_F_KERNEL flag for catchall elements in + the cache + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit dc6950a80110d6e6f63bd6f5c308d202db698f46 + +commit dc6950a80110d6e6f63bd6f5c308d202db698f46 +Author: Pablo Neira Ayuso +Date: Thu Jul 4 14:35:07 2024 +0200 + + segtree: set on EXPR_F_KERNEL flag for catchall elements in the cache + + Catchall set element deletion requires this flag to be set on, + otherwise it bogusly reports that such element does not exist + in the set. + + Fixes: f1cc44edb218 ("src: add EXPR_F_KERNEL to identify expression in the kernel") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/segtree.c | 4 +++- + 1 file changed, 3 insertions(+), 1 deletion(-) + +diff --git a/src/segtree.c b/src/segtree.c +index 7d246de..c5f27c9 100644 +--- a/src/segtree.c ++++ b/src/segtree.c +@@ -653,8 +653,10 @@ void interval_map_decompose(struct expr *set) + expr_free(i); + + out: +- if (catchall) ++ if (catchall) { ++ catchall->flags |= EXPR_F_KERNEL; + compound_expr_add(set, catchall); ++ } + + free(ranges); + free(elements); diff --git a/0105-optimize-clone-counter-before-insertion-into-set-ele.patch b/0105-optimize-clone-counter-before-insertion-into-set-ele.patch new file mode 100644 index 0000000..a067c68 --- /dev/null +++ b/0105-optimize-clone-counter-before-insertion-into-set-ele.patch @@ -0,0 +1,143 @@ +From 6804a55496761b1366ffa5e554f9658cfd55548a Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:10:08 +0200 +Subject: [PATCH] optimize: clone counter before insertion into set element + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit ac77f3805c71f14c51730a9c5cb726ee67f14159 + +commit ac77f3805c71f14c51730a9c5cb726ee67f14159 +Author: Pablo Neira Ayuso +Date: Fri Jul 5 14:03:33 2024 +0200 + + optimize: clone counter before insertion into set element + + The counter statement that is zapped from the rule needs to be cloned + before inserting it into each set element. + + Fixes: 686ab8b6996e ("optimize: do not remove counter in verdict maps") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/optimize.c | 24 +++++++++++++------ + .../optimizations/dumps/merge_counter.nft | 8 +++++++ + .../testcases/optimizations/merge_counter | 20 ++++++++++++++++ + 3 files changed, 45 insertions(+), 7 deletions(-) + create mode 100644 tests/shell/testcases/optimizations/dumps/merge_counter.nft + create mode 100755 tests/shell/testcases/optimizations/merge_counter + +diff --git a/src/optimize.c b/src/optimize.c +index 1dd0858..62dd908 100644 +--- a/src/optimize.c ++++ b/src/optimize.c +@@ -692,29 +692,36 @@ static void build_verdict_map(struct expr *expr, struct stmt *verdict, + struct expr *set, struct stmt *counter) + { + struct expr *item, *elem, *mapping; ++ struct stmt *counter_elem; + + switch (expr->etype) { + case EXPR_LIST: + list_for_each_entry(item, &expr->expressions, list) { + elem = set_elem_expr_alloc(&internal_location, expr_get(item)); +- if (counter) +- list_add_tail(&counter->list, &elem->stmt_list); ++ if (counter) { ++ counter_elem = counter_stmt_alloc(&counter->location); ++ list_add_tail(&counter_elem->list, &elem->stmt_list); ++ } + + mapping = mapping_expr_alloc(&internal_location, elem, + expr_get(verdict->expr)); + compound_expr_add(set, mapping); + } ++ stmt_free(counter); + break; + case EXPR_SET: + list_for_each_entry(item, &expr->expressions, list) { + elem = set_elem_expr_alloc(&internal_location, expr_get(item->key)); +- if (counter) +- list_add_tail(&counter->list, &elem->stmt_list); ++ if (counter) { ++ counter_elem = counter_stmt_alloc(&counter->location); ++ list_add_tail(&counter_elem->list, &elem->stmt_list); ++ } + + mapping = mapping_expr_alloc(&internal_location, elem, + expr_get(verdict->expr)); + compound_expr_add(set, mapping); + } ++ stmt_free(counter); + break; + case EXPR_PREFIX: + case EXPR_RANGE: +@@ -819,8 +826,8 @@ static void __merge_concat_stmts_vmap(const struct optimize_ctx *ctx, + struct expr *set, struct stmt *verdict) + { + struct expr *concat, *next, *elem, *mapping; ++ struct stmt *counter, *counter_elem; + LIST_HEAD(concat_list); +- struct stmt *counter; + + counter = zap_counter(ctx, i); + __merge_concat(ctx, i, merge, &concat_list); +@@ -828,13 +835,16 @@ static void __merge_concat_stmts_vmap(const struct optimize_ctx *ctx, + list_for_each_entry_safe(concat, next, &concat_list, list) { + list_del(&concat->list); + elem = set_elem_expr_alloc(&internal_location, concat); +- if (counter) +- list_add_tail(&counter->list, &elem->stmt_list); ++ if (counter) { ++ counter_elem = counter_stmt_alloc(&counter->location); ++ list_add_tail(&counter_elem->list, &elem->stmt_list); ++ } + + mapping = mapping_expr_alloc(&internal_location, elem, + expr_get(verdict->expr)); + compound_expr_add(set, mapping); + } ++ stmt_free(counter); + } + + static void merge_concat_stmts_vmap(const struct optimize_ctx *ctx, +diff --git a/tests/shell/testcases/optimizations/dumps/merge_counter.nft b/tests/shell/testcases/optimizations/dumps/merge_counter.nft +new file mode 100644 +index 0000000..72eed5d +--- /dev/null ++++ b/tests/shell/testcases/optimizations/dumps/merge_counter.nft +@@ -0,0 +1,8 @@ ++table ip x { ++ chain y { ++ type filter hook input priority filter; policy drop; ++ ct state vmap { invalid counter packets 0 bytes 0 : drop, established counter packets 0 bytes 0 : accept, related counter packets 0 bytes 0 : accept } ++ tcp dport { 80, 123 } counter packets 0 bytes 0 accept ++ ip saddr . ip daddr vmap { 1.1.1.1 . 2.2.2.2 counter packets 0 bytes 0 : accept, 1.1.1.2 . 3.3.3.3 counter packets 0 bytes 0 : drop } ++ } ++} +diff --git a/tests/shell/testcases/optimizations/merge_counter b/tests/shell/testcases/optimizations/merge_counter +new file mode 100755 +index 0000000..3b8bbad +--- /dev/null ++++ b/tests/shell/testcases/optimizations/merge_counter +@@ -0,0 +1,20 @@ ++#!/bin/bash ++ ++# NFT_TEST_REQUIRES(NFT_TEST_HAVE_set_expr) ++ ++set -e ++ ++RULESET="table ip x { ++ chain y { ++ type filter hook input priority 0; policy drop; ++ ++ ct state invalid counter drop ++ ct state established,related counter accept ++ tcp dport 80 counter accept ++ tcp dport 123 counter accept ++ ip saddr 1.1.1.1 ip daddr 2.2.2.2 counter accept ++ ip saddr 1.1.1.2 ip daddr 3.3.3.3 counter drop ++ } ++}" ++ ++$NFT -o -f - <<< $RULESET diff --git a/0106-libnftables-skip-useable-checks-for-dev-stdin.patch b/0106-libnftables-skip-useable-checks-for-dev-stdin.patch new file mode 100644 index 0000000..6c3afab --- /dev/null +++ b/0106-libnftables-skip-useable-checks-for-dev-stdin.patch @@ -0,0 +1,63 @@ +From 36abb7d32f0f4af37112fb74b31a7235d029e3e3 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:10:08 +0200 +Subject: [PATCH] libnftables: skip useable checks for /dev/stdin + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 477fd8218777b75bdfa3a5643f692adae4f002fe + +commit 477fd8218777b75bdfa3a5643f692adae4f002fe +Author: Pablo Neira Ayuso +Date: Tue Jul 9 16:59:53 2024 +0200 + + libnftables: skip useable checks for /dev/stdin + + /dev/stdin is a placeholder, read() from STDIN_FILENO is used to fetch + the standard input into a buffer. + + Since 5c2b2b0a2ba7 ("src: error reporting with -f and read from stdin") + stdin is stored in a buffer to fix error reporting. + + This patch requires: ("parser_json: use stdin buffer if available") + + Fixes: 149b1c95d129 ("libnftables: refuse to open onput files other than named pipes or regular files") + Acked-by: Phil Sutter + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/libnftables.c | 7 ++++--- + 1 file changed, 4 insertions(+), 3 deletions(-) + +diff --git a/src/libnftables.c b/src/libnftables.c +index 0dee1ba..8f8acd1 100644 +--- a/src/libnftables.c ++++ b/src/libnftables.c +@@ -664,6 +664,7 @@ retry: + + /* need to use stat() to, fopen() will block for named fifos and + * libjansson makes no checks before or after open either. ++ * /dev/stdin is *never* used, read() from STDIN_FILENO is used instead. + */ + static struct error_record *filename_is_useable(struct nft_ctx *nft, const char *name) + { +@@ -671,6 +672,9 @@ static struct error_record *filename_is_useable(struct nft_ctx *nft, const char + struct stat sb; + int err; + ++ if (!strcmp(name, "/dev/stdin")) ++ return NULL; ++ + err = stat(name, &sb); + if (err) + return error(&internal_location, "Could not open file \"%s\": %s\n", +@@ -681,9 +685,6 @@ static struct error_record *filename_is_useable(struct nft_ctx *nft, const char + if (type == S_IFREG || type == S_IFIFO) + return NULL; + +- if (type == S_IFCHR && 0 == strcmp(name, "/dev/stdin")) +- return NULL; +- + return error(&internal_location, "Not a regular file: \"%s\"\n", name); + } + diff --git a/0107-parser_json-use-stdin-buffer-if-available.patch b/0107-parser_json-use-stdin-buffer-if-available.patch new file mode 100644 index 0000000..adbe310 --- /dev/null +++ b/0107-parser_json-use-stdin-buffer-if-available.patch @@ -0,0 +1,63 @@ +From 50d322fb2d90b0a07b3f2e6544dec853ac22ac85 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:10:08 +0200 +Subject: [PATCH] parser_json: use stdin buffer if available + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit e48f32701ff65d522c2f29f34bf4f3ce8e562057 + +commit e48f32701ff65d522c2f29f34bf4f3ce8e562057 +Author: Pablo Neira Ayuso +Date: Tue Jul 9 16:59:52 2024 +0200 + + parser_json: use stdin buffer if available + + Since 5c2b2b0a2ba7 ("src: error reporting with -f and read from stdin") + stdin is stored in a buffer, update json support to use it instead of + reading from /dev/stdin. + + Some systems do not provide /dev/stdin symlink to /proc/self/fd/0 + according to reporter (that mentions Yocto Linux as example). + + Fixes: 935f82e7dd49 ("Support 'nft -f -' to read from stdin") + Acked-by: Phil Sutter + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/libnftables.c | 3 +-- + src/parser_json.c | 7 +++++++ + 2 files changed, 8 insertions(+), 2 deletions(-) + +diff --git a/src/libnftables.c b/src/libnftables.c +index 8f8acd1..02570c4 100644 +--- a/src/libnftables.c ++++ b/src/libnftables.c +@@ -795,8 +795,7 @@ int nft_run_cmd_from_filename(struct nft_ctx *nft, const char *filename) + if (!strcmp(filename, "-")) + filename = "/dev/stdin"; + +- if (!strcmp(filename, "/dev/stdin") && +- !nft_output_json(&nft->output)) ++ if (!strcmp(filename, "/dev/stdin")) + nft->stdin_buf = stdin_to_buffer(); + + if (nft->optimize_flags) { +diff --git a/src/parser_json.c b/src/parser_json.c +index ce36397..047c6fa 100644 +--- a/src/parser_json.c ++++ b/src/parser_json.c +@@ -4333,6 +4333,13 @@ int nft_parse_json_filename(struct nft_ctx *nft, const char *filename, + json_error_t err; + int ret; + ++ if (nft->stdin_buf) { ++ json_indesc.type = INDESC_STDIN; ++ json_indesc.name = "/dev/stdin"; ++ ++ return nft_parse_json_buffer(nft, nft->stdin_buf, msgs, cmds); ++ } ++ + json_indesc.type = INDESC_FILE; + json_indesc.name = filename; + diff --git a/0108-optimize-skip-variables-in-nat-statements.patch b/0108-optimize-skip-variables-in-nat-statements.patch new file mode 100644 index 0000000..764e185 --- /dev/null +++ b/0108-optimize-skip-variables-in-nat-statements.patch @@ -0,0 +1,111 @@ +From 0df57d34d73abae0b0a5e9530cc66aac0eda250b Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:10:08 +0200 +Subject: [PATCH] optimize: skip variables in nat statements + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit bc1f910f502701f1a1d28c7bd723e4be3bac1d8c + +commit bc1f910f502701f1a1d28c7bd723e4be3bac1d8c +Author: Pablo Neira Ayuso +Date: Thu Jul 18 18:06:22 2024 +0200 + + optimize: skip variables in nat statements + + Do not hit assert(): + + nft: optimize.c:486: rule_build_stmt_matrix_stmts: Assertion `k >= 0' failed. + + variables are not supported by -o/--optimize at this stage. + + Fixes: 9be404a153bc ("optimize: ignore existing nat mapping") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/optimize.c | 6 ++- + tests/shell/testcases/optimizations/variables | 52 ++++++++++++++++--- + 2 files changed, 49 insertions(+), 9 deletions(-) + +diff --git a/src/optimize.c b/src/optimize.c +index 62dd908..9f0965c 100644 +--- a/src/optimize.c ++++ b/src/optimize.c +@@ -408,9 +408,11 @@ static int rule_collect_stmts(struct optimize_ctx *ctx, struct rule *rule) + break; + case STMT_NAT: + if ((stmt->nat.addr && +- stmt->nat.addr->etype == EXPR_MAP) || ++ (stmt->nat.addr->etype == EXPR_MAP || ++ stmt->nat.addr->etype == EXPR_VARIABLE)) || + (stmt->nat.proto && +- stmt->nat.proto->etype == EXPR_MAP)) { ++ (stmt->nat.proto->etype == EXPR_MAP || ++ stmt->nat.proto->etype == EXPR_VARIABLE))) { + clone->ops = &unsupported_stmt_ops; + break; + } +diff --git a/tests/shell/testcases/optimizations/variables b/tests/shell/testcases/optimizations/variables +index fa98606..4cb322d 100755 +--- a/tests/shell/testcases/optimizations/variables ++++ b/tests/shell/testcases/optimizations/variables +@@ -2,14 +2,52 @@ + + set -e + +-RULESET="define addrv4_vpnnet = 10.1.0.0/16 ++RULESET='define addrv4_vpnnet = 10.1.0.0/16 ++define wan = "eth0" ++define lan = "eth1" ++define vpn = "tun0" ++define server = "10.10.10.1" + +-table ip nat { +- chain postrouting { +- type nat hook postrouting priority 0; policy accept; ++table inet filter { ++ chain input { ++ type filter hook input priority 0; policy drop; ++ } ++ chain forward { ++ type filter hook forward priority 1; policy drop; + +- ip saddr \$addrv4_vpnnet counter masquerade fully-random comment \"masquerade ipv4\" +- } +-}" ++ iifname $lan oifname $lan accept; ++ ++ iifname $lan oifname $wan ct state new accept ++ iifname $lan oifname $wan ct state {established, related} accept ++ ++ iifname $wan oifname $lan ct state {established, related} accept ++ ++ iifname $vpn oifname $wan accept ++ iifname $wan oifname $vpn accept ++ iifname $lan oifname $vpn accept ++ iifname $vpn oifname $lan accept ++ ++ iifname $lan oifname $server accept ++ iifname $server oifname $lan accept ++ iifname $server oifname $wan accept ++ iifname $wan oifname $server accept ++ } ++ chain output { ++ type filter hook output priority 0; policy drop; ++ } ++} ++ ++table nat { ++ chain prerouting { ++ type nat hook prerouting priority -100; policy accept; ++ iifname $wan tcp dport 10000 dnat to $server:10000; ++ } ++ chain postrouting { ++ type nat hook postrouting priority 100; policy accept; ++ ip saddr $addrv4_vpnnet counter masquerade fully-random comment "masquerade ipv4" ++ oifname $vpn masquerade ++ oifname $wan masquerade ++ } ++}' + + $NFT -c -o -f - <<< $RULESET diff --git a/0109-datatype-reject-rate-in-quota-statement.patch b/0109-datatype-reject-rate-in-quota-statement.patch new file mode 100644 index 0000000..ac54ef0 --- /dev/null +++ b/0109-datatype-reject-rate-in-quota-statement.patch @@ -0,0 +1,77 @@ +From 2269f1c988af5f779c39b452231c6cd841bdc019 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:10:09 +0200 +Subject: [PATCH] datatype: reject rate in quota statement + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 8ed6fa6d66b2df50d118423c1cb0e98cdd45cdbd + +commit 8ed6fa6d66b2df50d118423c1cb0e98cdd45cdbd +Author: Pablo Neira Ayuso +Date: Wed Aug 14 13:02:02 2024 +0200 + + datatype: reject rate in quota statement + + Bail out if rate are used: + + ruleset.nft:5:77-106: Error: Wrong rate format, expecting bytes or kbytes or mbytes + add rule netdev firewall PROTECTED_IPS update @quota_temp_before { ip daddr quota over 45000 mbytes/second } add @quota_trigger { ip daddr } + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + improve error reporting while at this. + + Fixes: 6615676d825e ("src: add per-bytes limit") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/datatype.c | 20 +++++++++++++------- + 1 file changed, 13 insertions(+), 7 deletions(-) + +diff --git a/src/datatype.c b/src/datatype.c +index 46d77eb..4cceeb8 100644 +--- a/src/datatype.c ++++ b/src/datatype.c +@@ -1432,14 +1432,14 @@ static struct error_record *time_unit_parse(const struct location *loc, + struct error_record *data_unit_parse(const struct location *loc, + const char *str, uint64_t *rate) + { +- if (strncmp(str, "bytes", strlen("bytes")) == 0) ++ if (strcmp(str, "bytes") == 0) + *rate = 1ULL; +- else if (strncmp(str, "kbytes", strlen("kbytes")) == 0) ++ else if (strcmp(str, "kbytes") == 0) + *rate = 1024; +- else if (strncmp(str, "mbytes", strlen("mbytes")) == 0) ++ else if (strcmp(str, "mbytes") == 0) + *rate = 1024 * 1024; + else +- return error(loc, "Wrong rate format"); ++ return error(loc, "Wrong unit format, expecting bytes, kbytes or mbytes"); + + return NULL; + } +@@ -1447,14 +1447,20 @@ struct error_record *data_unit_parse(const struct location *loc, + struct error_record *rate_parse(const struct location *loc, const char *str, + uint64_t *rate, uint64_t *unit) + { ++ const char *slash, *rate_str; + struct error_record *erec; +- const char *slash; + + slash = strchr(str, '/'); + if (!slash) +- return error(loc, "wrong rate format"); ++ return error(loc, "wrong rate format, expecting {bytes,kbytes,mbytes}/{second,minute,hour,day,week}"); ++ ++ rate_str = strndup(str, slash - str); ++ if (!rate_str) ++ memory_allocation_error(); ++ ++ erec = data_unit_parse(loc, rate_str, rate); ++ free_const(rate_str); + +- erec = data_unit_parse(loc, str, rate); + if (erec != NULL) + return erec; + diff --git a/0110-cache-rule-by-index-requires-full-cache.patch b/0110-cache-rule-by-index-requires-full-cache.patch new file mode 100644 index 0000000..711eb9d --- /dev/null +++ b/0110-cache-rule-by-index-requires-full-cache.patch @@ -0,0 +1,45 @@ +From 65ef3c4192edf76e2744646e64003fbf8da01311 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:10:09 +0200 +Subject: [PATCH] cache: rule by index requires full cache + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 161beaeacd2e5218d66febc3db825bf6a27119c5 + +commit 161beaeacd2e5218d66febc3db825bf6a27119c5 +Author: Pablo Neira Ayuso +Date: Thu Aug 15 12:34:08 2024 +0200 + + cache: rule by index requires full cache + + In preparation for on-demand cache population with errors, set on + NFT_CACHE_FULL if rule index is used since this requires a full cache + with rules. + + This is not a fix, index is already fetching a full cache before this + patch. + + But follow up patches relax cache requirements, so add this patch in + first place to make sure index does not break. + + Tested-by: Eric Garver + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/cache.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/cache.c b/src/cache.c +index e88cbae..42e60df 100644 +--- a/src/cache.c ++++ b/src/cache.c +@@ -68,7 +68,7 @@ static unsigned int evaluate_cache_add(struct cmd *cmd, unsigned int flags) + + if (cmd->handle.index.id || + cmd->handle.position.id) +- flags |= NFT_CACHE_RULE | NFT_CACHE_UPDATE; ++ flags |= NFT_CACHE_FULL | NFT_CACHE_UPDATE; + break; + default: + break; diff --git a/0111-datatype-improve-error-reporting-when-time-unit-is-n.patch b/0111-datatype-improve-error-reporting-when-time-unit-is-n.patch new file mode 100644 index 0000000..5404195 --- /dev/null +++ b/0111-datatype-improve-error-reporting-when-time-unit-is-n.patch @@ -0,0 +1,44 @@ +From 1d307c03b99a145202aa08f28f1665fc739fd686 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:10:09 +0200 +Subject: [PATCH] datatype: improve error reporting when time unit is not + correct + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 6bcaef6a1ea6dc60250ed6124f3b49a8cd29434c + +commit 6bcaef6a1ea6dc60250ed6124f3b49a8cd29434c +Author: Pablo Neira Ayuso +Date: Wed Aug 14 13:05:54 2024 +0200 + + datatype: improve error reporting when time unit is not correct + + Display: + + Wrong unit format, expecting bytes or kbytes or mbytes + + instead of: + + Wrong rate format + + Fixes: 6615676d825e ("src: add per-bytes limit") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/datatype.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/datatype.c b/src/datatype.c +index 4cceeb8..c94c839 100644 +--- a/src/datatype.c ++++ b/src/datatype.c +@@ -1424,7 +1424,7 @@ static struct error_record *time_unit_parse(const struct location *loc, + else if (strcmp(str, "week") == 0) + *unit = 1ULL * 60 * 60 * 24 * 7; + else +- return error(loc, "Wrong rate format"); ++ return error(loc, "Wrong time format, expecting second, minute, hour, day or week"); + + return NULL; + } diff --git a/0112-parser_bison-allow-0-burst-in-limit-rate-byte-mode.patch b/0112-parser_bison-allow-0-burst-in-limit-rate-byte-mode.patch new file mode 100644 index 0000000..28bb2a6 --- /dev/null +++ b/0112-parser_bison-allow-0-burst-in-limit-rate-byte-mode.patch @@ -0,0 +1,91 @@ +From 98811a10653288ee3ceae5b9b9324ff56d70f507 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:10:09 +0200 +Subject: [PATCH] parser_bison: allow 0 burst in limit rate byte mode +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit cea05ae5bdc50949d4c734796d6db5717187055a + +commit cea05ae5bdc50949d4c734796d6db5717187055a +Author: Pablo Neira Ayuso +Date: Thu Aug 15 13:56:21 2024 +0200 + + parser_bison: allow 0 burst in limit rate byte mode + + Unbreak restoring elements in set with rate limit that fail with: + + > /dev/stdin:3618:61-61: Error: limit burst must be > 0 + >                  elements = { 1.2.3.4 limit rate over 1000 kbytes/second timeout 1s, + + no need for burst != 0 for limit rate byte mode. + + Add tests/shell too. + + Fixes: 702eff5b5b74 ("src: allow burst 0 for byte ratelimit and use it as default") + Fixes: 285baccfea46 ("src: disallow burst 0 in ratelimits") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/parser_bison.y | 5 ----- + .../shell/testcases/sets/dumps/elem_limit_0.nft | 7 +++++++ + tests/shell/testcases/sets/elem_limit_0 | 17 +++++++++++++++++ + 3 files changed, 24 insertions(+), 5 deletions(-) + create mode 100644 tests/shell/testcases/sets/dumps/elem_limit_0.nft + create mode 100755 tests/shell/testcases/sets/elem_limit_0 + +diff --git a/src/parser_bison.y b/src/parser_bison.y +index 87cf9ae..c18e739 100644 +--- a/src/parser_bison.y ++++ b/src/parser_bison.y +@@ -4589,11 +4589,6 @@ set_elem_stmt : COUNTER close_scope_counter + } + | LIMIT RATE limit_mode limit_rate_bytes limit_burst_bytes close_scope_limit + { +- if ($5 == 0) { +- erec_queue(error(&@6, "limit burst must be > 0"), +- state->msgs); +- YYERROR; +- } + $$ = limit_stmt_alloc(&@$); + $$->limit.rate = $4.rate; + $$->limit.unit = $4.unit; +diff --git a/tests/shell/testcases/sets/dumps/elem_limit_0.nft b/tests/shell/testcases/sets/dumps/elem_limit_0.nft +new file mode 100644 +index 0000000..ca5b2b5 +--- /dev/null ++++ b/tests/shell/testcases/sets/dumps/elem_limit_0.nft +@@ -0,0 +1,7 @@ ++table netdev filter { ++ set test123 { ++ typeof ip saddr ++ limit rate over 1 mbytes/second ++ elements = { 1.2.3.4 limit rate over 1 mbytes/second } ++ } ++} +diff --git a/tests/shell/testcases/sets/elem_limit_0 b/tests/shell/testcases/sets/elem_limit_0 +new file mode 100755 +index 0000000..b57f927 +--- /dev/null ++++ b/tests/shell/testcases/sets/elem_limit_0 +@@ -0,0 +1,17 @@ ++#!/bin/bash ++ ++## requires EXPR ++ ++set -e ++ ++RULESET="table netdev filter { ++ set test123 { ++ typeof ip saddr ++ limit rate over 1024 kbytes/second ++ elements = { 1.2.3.4 limit rate over 1024 kbytes/second } ++ } ++}" ++ ++$NFT -f - <<< $RULESET ++ ++(echo "flush ruleset netdev"; $NFT --stateless list ruleset netdev) | $NFT -f - diff --git a/0113-parser_json-fix-crash-in-json_parse_set_stmt_list.patch b/0113-parser_json-fix-crash-in-json_parse_set_stmt_list.patch new file mode 100644 index 0000000..5a3630b --- /dev/null +++ b/0113-parser_json-fix-crash-in-json_parse_set_stmt_list.patch @@ -0,0 +1,56 @@ +From a15ffca5581d098e3f03cbbe48edae886d0d60df Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:10:09 +0200 +Subject: [PATCH] parser_json: fix crash in json_parse_set_stmt_list + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 26d9cbefb10e6bc3765df7e9e7a4fc3b951a80f3 + +commit 26d9cbefb10e6bc3765df7e9e7a4fc3b951a80f3 +Author: Sebastian Walz (sivizius) +Date: Tue Aug 20 00:09:26 2024 +0200 + + parser_json: fix crash in json_parse_set_stmt_list + + Due to missing `NULL`-check, there will be a segfault for invalid statements. + + Fixes: 07958ec53830 ("json: add set statement list support") + Signed-off-by: Sebastian Walz (sivizius) + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/parser_json.c | 13 +++++++++---- + 1 file changed, 9 insertions(+), 4 deletions(-) + +diff --git a/src/parser_json.c b/src/parser_json.c +index 047c6fa..523dbd2 100644 +--- a/src/parser_json.c ++++ b/src/parser_json.c +@@ -2349,7 +2349,7 @@ static void json_parse_set_stmt_list(struct json_ctx *ctx, + json_t *stmt_json) + { + struct list_head *head; +- struct stmt *tmp; ++ struct stmt *stmt; + json_t *value; + size_t index; + +@@ -2361,9 +2361,14 @@ static void json_parse_set_stmt_list(struct json_ctx *ctx, + + head = stmt_list; + json_array_foreach(stmt_json, index, value) { +- tmp = json_parse_stmt(ctx, value); +- list_add(&tmp->list, head); +- head = &tmp->list; ++ stmt = json_parse_stmt(ctx, value); ++ if (!stmt) { ++ json_error(ctx, "Parsing set statements array at index %zd failed.", index); ++ stmt_list_free(stmt_list); ++ return; ++ } ++ list_add(&stmt->list, head); ++ head = &stmt->list; + } + } + diff --git a/0114-json-Support-maps-with-concatenated-data.patch b/0114-json-Support-maps-with-concatenated-data.patch new file mode 100644 index 0000000..1eb3386 --- /dev/null +++ b/0114-json-Support-maps-with-concatenated-data.patch @@ -0,0 +1,99 @@ +From 47ee410b237d907322baed7f01b6dc0abbb6fe29 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:10:09 +0200 +Subject: [PATCH] json: Support maps with concatenated data + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit c56d77cc82988391ab8f2514214c0088cbc7d89e + +commit c56d77cc82988391ab8f2514214c0088cbc7d89e +Author: Phil Sutter +Date: Sat Mar 9 00:27:38 2024 +0100 + + json: Support maps with concatenated data + + Dump such maps with an array of types in "map" property, make the parser + aware of this. + + Signed-off-by: Phil Sutter + +Signed-off-by: Phil Sutter +--- + src/json.c | 10 +++++----- + src/parser_json.c | 18 +++++++++--------- + 2 files changed, 14 insertions(+), 14 deletions(-) + +diff --git a/src/json.c b/src/json.c +index 5faeb4d..90d9795 100644 +--- a/src/json.c ++++ b/src/json.c +@@ -130,15 +130,15 @@ static json_t *set_stmt_list_json(const struct list_head *stmt_list, + + static json_t *set_print_json(struct output_ctx *octx, const struct set *set) + { +- json_t *root, *tmp; +- const char *type, *datatype_ext = NULL; ++ json_t *root, *tmp, *datatype_ext = NULL; ++ const char *type; + + if (set_is_datamap(set->flags)) { + type = "map"; +- datatype_ext = set->data->dtype->name; ++ datatype_ext = set_dtype_json(set->data); + } else if (set_is_objmap(set->flags)) { + type = "map"; +- datatype_ext = obj_type_name(set->objtype); ++ datatype_ext = json_string(obj_type_name(set->objtype)); + } else if (set_is_meter(set->flags)) { + type = "meter"; + } else { +@@ -155,7 +155,7 @@ static json_t *set_print_json(struct output_ctx *octx, const struct set *set) + if (set->comment) + json_object_set_new(root, "comment", json_string(set->comment)); + if (datatype_ext) +- json_object_set_new(root, "map", json_string(datatype_ext)); ++ json_object_set_new(root, "map", datatype_ext); + + if (!(set->flags & (NFT_SET_CONSTANT))) { + if (set->policy != NFT_SET_POL_PERFORMANCE) { +diff --git a/src/parser_json.c b/src/parser_json.c +index 523dbd2..a144f4c 100644 +--- a/src/parser_json.c ++++ b/src/parser_json.c +@@ -3300,7 +3300,7 @@ static struct cmd *json_parse_cmd_add_set(struct json_ctx *ctx, json_t *root, + enum cmd_ops op, enum cmd_obj obj) + { + struct handle h = { 0 }; +- const char *family = "", *policy, *dtype_ext = NULL; ++ const char *family = "", *policy; + json_t *tmp, *stmt_json; + struct set *set; + +@@ -3353,19 +3353,19 @@ static struct cmd *json_parse_cmd_add_set(struct json_ctx *ctx, json_t *root, + return NULL; + } + +- if (!json_unpack(root, "{s:s}", "map", &dtype_ext)) { +- const struct datatype *dtype; ++ if (!json_unpack(root, "{s:o}", "map", &tmp)) { ++ if (json_is_string(tmp)) { ++ const char *s = json_string_value(tmp); + +- set->objtype = string_to_nft_object(dtype_ext); ++ set->objtype = string_to_nft_object(s); ++ } + if (set->objtype) { + set->flags |= NFT_SET_OBJECT; +- } else if ((dtype = datatype_lookup_byname(dtype_ext))) { +- set->data = constant_expr_alloc(&netlink_location, +- dtype, dtype->byteorder, +- dtype->size, NULL); ++ } else if ((set->data = json_parse_dtype_expr(ctx, tmp))) { + set->flags |= NFT_SET_MAP; + } else { +- json_error(ctx, "Invalid map type '%s'.", dtype_ext); ++ json_error(ctx, "Invalid map type '%s'.", ++ json_dumps(tmp, 0)); + set_free(set); + handle_free(&h); + return NULL; diff --git a/0115-parser_json-release-buffer-returned-by-json_dumps.patch b/0115-parser_json-release-buffer-returned-by-json_dumps.patch new file mode 100644 index 0000000..c60fd17 --- /dev/null +++ b/0115-parser_json-release-buffer-returned-by-json_dumps.patch @@ -0,0 +1,62 @@ +From 281ca241e91da463f1f7115acbe25322b5b28b64 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:10:09 +0200 +Subject: [PATCH] parser_json: release buffer returned by json_dumps + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 46700fbdbbbaab0d7db716fce3a438334c58ac9e + +commit 46700fbdbbbaab0d7db716fce3a438334c58ac9e +Author: Sebastian Walz (sivizius) +Date: Mon Aug 19 19:58:14 2024 +0200 + + parser_json: release buffer returned by json_dumps + + The signature of `json_dumps` is: + + `char *json_dumps(const json_t *json, size_t flags)`: + + It will return a pointer to an owned string, the caller must free it. + However, `json_error` just borrows the string to format it as `%s`, but + after printing the formatted error message, the pointer to the string is + lost and thus never freed. + + Fixes: 586ad210368b ("libnftables: Implement JSON parser") + Signed-off-by: Sebastian Walz (sivizius) + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/parser_json.c | 11 ++++++++--- + 1 file changed, 8 insertions(+), 3 deletions(-) + +diff --git a/src/parser_json.c b/src/parser_json.c +index a144f4c..3473a2a 100644 +--- a/src/parser_json.c ++++ b/src/parser_json.c +@@ -181,8 +181,11 @@ static int json_unpack_stmt(struct json_ctx *ctx, json_t *root, + assert(value); + + if (json_object_size(root) != 1) { ++ const char *dump = json_dumps(root, 0); ++ + json_error(ctx, "Malformed object (too many properties): '%s'.", +- json_dumps(root, 0)); ++ dump); ++ free_const(dump); + return 1; + } + +@@ -3364,8 +3367,10 @@ static struct cmd *json_parse_cmd_add_set(struct json_ctx *ctx, json_t *root, + } else if ((set->data = json_parse_dtype_expr(ctx, tmp))) { + set->flags |= NFT_SET_MAP; + } else { +- json_error(ctx, "Invalid map type '%s'.", +- json_dumps(tmp, 0)); ++ const char *dump = json_dumps(tmp, 0); ++ ++ json_error(ctx, "Invalid map type '%s'.", dump); ++ free_const(dump); + set_free(set); + handle_free(&h); + return NULL; diff --git a/0116-parser_json-fix-several-expression-memleaks-from-err.patch b/0116-parser_json-fix-several-expression-memleaks-from-err.patch new file mode 100644 index 0000000..6b364bb --- /dev/null +++ b/0116-parser_json-fix-several-expression-memleaks-from-err.patch @@ -0,0 +1,52 @@ +From e13594e528eb47b798a9c6aecd1e27153678af26 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:10:09 +0200 +Subject: [PATCH] parser_json: fix several expression memleaks from error path + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit bae7b4d283826efbeb28c21aecd7b355e86da170 + +commit bae7b4d283826efbeb28c21aecd7b355e86da170 +Author: Sebastian Walz (sivizius) +Date: Mon Aug 19 20:11:44 2024 +0200 + + parser_json: fix several expression memleaks from error path + + Fixes: 586ad210368b ("libnftables: Implement JSON parser") + Signed-off-by: Sebastian Walz (sivizius) + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/parser_json.c | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/src/parser_json.c b/src/parser_json.c +index 3473a2a..381d0f8 100644 +--- a/src/parser_json.c ++++ b/src/parser_json.c +@@ -1283,6 +1283,7 @@ static struct expr *json_parse_range_expr(struct json_ctx *ctx, + expr_high = json_parse_primary_expr(ctx, high); + if (!expr_high) { + json_error(ctx, "Invalid high value in range expression."); ++ expr_free(expr_low); + return NULL; + } + return range_expr_alloc(int_loc, expr_low, expr_high); +@@ -1864,6 +1865,8 @@ static struct stmt *json_parse_mangle_stmt(struct json_ctx *ctx, + return stmt; + default: + json_error(ctx, "Invalid mangle statement key expression type."); ++ expr_free(key); ++ expr_free(value); + return NULL; + } + } +@@ -2868,6 +2871,7 @@ static struct stmt *json_parse_optstrip_stmt(struct json_ctx *ctx, + expr->etype != EXPR_EXTHDR || + expr->exthdr.op != NFT_EXTHDR_OP_TCPOPT) { + json_error(ctx, "Illegal TCP optstrip argument"); ++ expr_free(expr); + return NULL; + } + diff --git a/0117-cache-reset-filter-for-each-command.patch b/0117-cache-reset-filter-for-each-command.patch new file mode 100644 index 0000000..940951e --- /dev/null +++ b/0117-cache-reset-filter-for-each-command.patch @@ -0,0 +1,51 @@ +From e6e30f10da2708b692c5c5ec00db9343878e03ab Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:10:09 +0200 +Subject: [PATCH] cache: reset filter for each command + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 29cb49d0ca92b840938823dec697d8c5488d7253 + +commit 29cb49d0ca92b840938823dec697d8c5488d7253 +Author: Pablo Neira Ayuso +Date: Mon Aug 26 10:18:34 2024 +0200 + + cache: reset filter for each command + + Inconditionally reset filter for each command in the batch, this is safer. + + Fixes: 3f1d3912c3a6 ("cache: filter out tables that are not requested") + Tested-by: Eric Garver + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/cache.c | 8 ++++++-- + 1 file changed, 6 insertions(+), 2 deletions(-) + +diff --git a/src/cache.c b/src/cache.c +index 42e60df..365431e 100644 +--- a/src/cache.c ++++ b/src/cache.c +@@ -407,6 +407,11 @@ err_name_too_long: + return -1; + } + ++static void reset_filter(struct nft_cache_filter *filter) ++{ ++ memset(&filter->list, 0, sizeof(filter->list)); ++} ++ + int nft_cache_evaluate(struct nft_ctx *nft, struct list_head *cmds, + struct list_head *msgs, struct nft_cache_filter *filter, + unsigned int *pflags) +@@ -418,8 +423,7 @@ int nft_cache_evaluate(struct nft_ctx *nft, struct list_head *cmds, + if (nft_handle_validate(cmd, msgs) < 0) + return -1; + +- if (filter->list.table && cmd->op != CMD_LIST) +- memset(&filter->list, 0, sizeof(filter->list)); ++ reset_filter(filter); + + switch (cmd->op) { + case CMD_ADD: diff --git a/0118-cache-accumulate-flags-in-batch.patch b/0118-cache-accumulate-flags-in-batch.patch new file mode 100644 index 0000000..fec9428 --- /dev/null +++ b/0118-cache-accumulate-flags-in-batch.patch @@ -0,0 +1,78 @@ +From 32c5ed1dea1e3a1bb5889c640936d098e3e0691d Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:10:09 +0200 +Subject: [PATCH] cache: accumulate flags in batch + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 68c8fb5f7c988a38a694c77c65e789e0cb8dfd8a + +commit 68c8fb5f7c988a38a694c77c65e789e0cb8dfd8a +Author: Pablo Neira Ayuso +Date: Mon Aug 26 10:19:39 2024 +0200 + + cache: accumulate flags in batch + + Recent updates are relaxing cache requirements: + + babc6ee8773c ("cache: populate chains on demand from error path") + + Flags describe cache requirements for a given batch, accumulate flags + that are inferred from commands in this batch. + + Fixes: 7df42800cf89 ("src: single cache_update() call to build cache before evaluation") + Tested-by: Eric Garver + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/cache.c | 12 +++++++----- + 1 file changed, 7 insertions(+), 5 deletions(-) + +diff --git a/src/cache.c b/src/cache.c +index 365431e..3940ab5 100644 +--- a/src/cache.c ++++ b/src/cache.c +@@ -416,13 +416,14 @@ int nft_cache_evaluate(struct nft_ctx *nft, struct list_head *cmds, + struct list_head *msgs, struct nft_cache_filter *filter, + unsigned int *pflags) + { +- unsigned int flags = NFT_CACHE_EMPTY; ++ unsigned int flags, batch_flags = NFT_CACHE_EMPTY; + struct cmd *cmd; + + list_for_each_entry(cmd, cmds, list) { + if (nft_handle_validate(cmd, msgs) < 0) + return -1; + ++ flags = NFT_CACHE_EMPTY; + reset_filter(filter); + + switch (cmd->op) { +@@ -450,13 +451,13 @@ int nft_cache_evaluate(struct nft_ctx *nft, struct list_head *cmds, + flags = evaluate_cache_get(cmd, flags); + break; + case CMD_RESET: +- flags |= evaluate_cache_reset(cmd, flags, filter); ++ flags = evaluate_cache_reset(cmd, flags, filter); + break; + case CMD_LIST: +- flags |= evaluate_cache_list(nft, cmd, flags, filter); ++ flags = evaluate_cache_list(nft, cmd, flags, filter); + break; + case CMD_MONITOR: +- flags |= NFT_CACHE_FULL; ++ flags = NFT_CACHE_FULL; + break; + case CMD_FLUSH: + flags = evaluate_cache_flush(cmd, flags, filter); +@@ -471,8 +472,9 @@ int nft_cache_evaluate(struct nft_ctx *nft, struct list_head *cmds, + default: + break; + } ++ batch_flags |= flags; + } +- *pflags = flags; ++ *pflags = batch_flags; + + return 0; + } diff --git a/0119-cache-only-dump-rules-for-the-given-table.patch b/0119-cache-only-dump-rules-for-the-given-table.patch new file mode 100644 index 0000000..94334bd --- /dev/null +++ b/0119-cache-only-dump-rules-for-the-given-table.patch @@ -0,0 +1,40 @@ +From b177524ecebef7e317841415c9ac2cae43c1dde8 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:10:09 +0200 +Subject: [PATCH] cache: only dump rules for the given table + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit ebd06f85a3257c294572005d0fa6b8ab0f213486 + +commit ebd06f85a3257c294572005d0fa6b8ab0f213486 +Author: Pablo Neira Ayuso +Date: Mon Aug 26 00:41:40 2024 +0200 + + cache: only dump rules for the given table + + Only family is set on in the dump request, set on table and chain + otherwise, rules for the given family are fetched for each existing + table. + + Fixes: afbd102211dc ("src: do not use the nft_cache_filter object from mnl.c") + Tested-by: Eric Garver + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/cache.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/cache.c b/src/cache.c +index 3940ab5..eb00480 100644 +--- a/src/cache.c ++++ b/src/cache.c +@@ -658,7 +658,7 @@ int rule_cache_dump(struct netlink_ctx *ctx, const struct handle *h, + bool dump, bool reset) + { + struct nftnl_rule_list *rule_cache; +- const char *table = NULL; ++ const char *table = h->table.name; + const char *chain = NULL; + uint64_t rule_handle = 0; + diff --git a/0120-cache-add-filtering-support-for-objects.patch b/0120-cache-add-filtering-support-for-objects.patch new file mode 100644 index 0000000..7c6a33d --- /dev/null +++ b/0120-cache-add-filtering-support-for-objects.patch @@ -0,0 +1,223 @@ +From 580a7c22f1b5419bf3231a7c863cd20784f03b8a Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:10:09 +0200 +Subject: [PATCH] cache: add filtering support for objects + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 969ce17b66f8084626610202f11d607911e049e6 + +commit 969ce17b66f8084626610202f11d607911e049e6 +Author: Pablo Neira Ayuso +Date: Mon Aug 26 00:41:37 2024 +0200 + + cache: add filtering support for objects + + Currently, full ruleset flag is set on to fetch objects. + + Follow a similar approach to these patches from Phil: + + de961b930660 ("cache: Filter set list on server side") and + cb4b07d0b628 ("cache: Support filtering for a specific flowtable") + + in preparation to update the reset command to use the cache + infrastructure. + + Tested-by: Eric Garver + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + include/cache.h | 2 + + src/cache.c | 100 +++++++++++++++++++++++++++++++++++++++++------- + 2 files changed, 89 insertions(+), 13 deletions(-) + +diff --git a/include/cache.h b/include/cache.h +index 8ca4a9a..e47dc88 100644 +--- a/include/cache.h ++++ b/include/cache.h +@@ -55,8 +55,10 @@ struct nft_cache_filter { + uint32_t family; + const char *table; + const char *chain; ++ const char *obj; + const char *set; + const char *ft; ++ int obj_type; + uint64_t rule_handle; + } list; + +diff --git a/src/cache.c b/src/cache.c +index eb00480..2407fc2 100644 +--- a/src/cache.c ++++ b/src/cache.c +@@ -197,6 +197,22 @@ static unsigned int evaluate_cache_rename(struct cmd *cmd, unsigned int flags) + return flags; + } + ++static void obj_filter_setup(const struct cmd *cmd, unsigned int *flags, ++ struct nft_cache_filter *filter, int type) ++{ ++ assert(filter); ++ ++ if (cmd->handle.family) ++ filter->list.family = cmd->handle.family; ++ if (cmd->handle.table.name) ++ filter->list.table = cmd->handle.table.name; ++ if (cmd->handle.obj.name) ++ filter->list.obj = cmd->handle.obj.name; ++ ++ filter->list.obj_type = type; ++ *flags |= NFT_CACHE_TABLE | NFT_CACHE_OBJECT; ++} ++ + static unsigned int evaluate_cache_list(struct nft_ctx *nft, struct cmd *cmd, + unsigned int flags, + struct nft_cache_filter *filter) +@@ -258,6 +274,37 @@ static unsigned int evaluate_cache_list(struct nft_ctx *nft, struct cmd *cmd, + case CMD_OBJ_FLOWTABLES: + flags |= NFT_CACHE_TABLE | NFT_CACHE_FLOWTABLE; + break; ++ case CMD_OBJ_COUNTER: ++ case CMD_OBJ_COUNTERS: ++ obj_filter_setup(cmd, &flags, filter, NFT_OBJECT_COUNTER); ++ break; ++ case CMD_OBJ_QUOTA: ++ case CMD_OBJ_QUOTAS: ++ obj_filter_setup(cmd, &flags, filter, NFT_OBJECT_QUOTA); ++ break; ++ case CMD_OBJ_CT_HELPER: ++ case CMD_OBJ_CT_HELPERS: ++ obj_filter_setup(cmd, &flags, filter, NFT_OBJECT_CT_HELPER); ++ break; ++ case CMD_OBJ_LIMIT: ++ case CMD_OBJ_LIMITS: ++ obj_filter_setup(cmd, &flags, filter, NFT_OBJECT_LIMIT); ++ break; ++ case CMD_OBJ_CT_TIMEOUT: ++ case CMD_OBJ_CT_TIMEOUTS: ++ obj_filter_setup(cmd, &flags, filter, NFT_OBJECT_CT_TIMEOUT); ++ break; ++ case CMD_OBJ_SECMARK: ++ case CMD_OBJ_SECMARKS: ++ obj_filter_setup(cmd, &flags, filter, NFT_OBJECT_SECMARK); ++ break; ++ case CMD_OBJ_CT_EXPECT: ++ obj_filter_setup(cmd, &flags, filter, NFT_OBJECT_CT_EXPECT); ++ break; ++ case CMD_OBJ_SYNPROXY: ++ case CMD_OBJ_SYNPROXYS: ++ obj_filter_setup(cmd, &flags, filter, NFT_OBJECT_SYNPROXY); ++ break; + case CMD_OBJ_RULESET: + default: + flags |= NFT_CACHE_FULL; +@@ -793,10 +840,19 @@ struct obj_cache_dump_ctx { + static int obj_cache_cb(struct nftnl_obj *nlo, void *arg) + { + struct obj_cache_dump_ctx *ctx = arg; ++ const char *obj_table; + const char *obj_name; ++ uint32_t obj_family; + struct obj *obj; + uint32_t hash; + ++ obj_table = nftnl_obj_get_str(nlo, NFTNL_OBJ_TABLE); ++ obj_family = nftnl_obj_get_u32(nlo, NFTNL_OBJ_FAMILY); ++ ++ if (obj_family != ctx->table->handle.family || ++ strcmp(obj_table, ctx->table->handle.table.name)) ++ return 0; ++ + obj = netlink_delinearize_obj(ctx->nlctx, nlo); + if (!obj) + return -1; +@@ -805,6 +861,9 @@ static int obj_cache_cb(struct nftnl_obj *nlo, void *arg) + hash = djb_hash(obj_name) % NFT_CACHE_HSIZE; + cache_add(&obj->cache, &ctx->table->obj_cache, hash); + ++ nftnl_obj_list_del(nlo); ++ nftnl_obj_free(nlo); ++ + return 0; + } + +@@ -821,13 +880,27 @@ static int obj_cache_init(struct netlink_ctx *ctx, struct table *table, + } + + static struct nftnl_obj_list *obj_cache_dump(struct netlink_ctx *ctx, +- const struct table *table) ++ const struct nft_cache_filter *filter) + { + struct nftnl_obj_list *obj_list; ++ int type = NFT_OBJECT_UNSPEC; ++ int family = NFPROTO_UNSPEC; ++ const char *table = NULL; ++ const char *obj = NULL; ++ bool dump = true; + +- obj_list = mnl_nft_obj_dump(ctx, table->handle.family, +- table->handle.table.name, NULL, +- 0, true, false); ++ if (filter) { ++ family = filter->list.family; ++ if (filter->list.table) ++ table = filter->list.table; ++ if (filter->list.obj) { ++ obj = filter->list.obj; ++ dump = false; ++ } ++ if (filter->list.obj_type) ++ type = filter->list.obj_type; ++ } ++ obj_list = mnl_nft_obj_dump(ctx, family, table, obj, type, dump, false); + if (!obj_list) { + if (errno == EINTR) + return NULL; +@@ -1050,7 +1123,7 @@ static int cache_init_objects(struct netlink_ctx *ctx, unsigned int flags, + struct nftnl_flowtable_list *ft_list = NULL; + struct nftnl_chain_list *chain_list = NULL; + struct nftnl_set_list *set_list = NULL; +- struct nftnl_obj_list *obj_list; ++ struct nftnl_obj_list *obj_list = NULL; + struct table *table; + struct set *set; + int ret = 0; +@@ -1067,6 +1140,13 @@ static int cache_init_objects(struct netlink_ctx *ctx, unsigned int flags, + goto cache_fails; + } + } ++ if (flags & NFT_CACHE_OBJECT_BIT) { ++ obj_list = obj_cache_dump(ctx, filter); ++ if (!obj_list) { ++ ret = -1; ++ goto cache_fails; ++ } ++ } + if (flags & NFT_CACHE_FLOWTABLE_BIT) { + ft_list = ft_cache_dump(ctx, filter); + if (!ft_list) { +@@ -1119,15 +1199,7 @@ static int cache_init_objects(struct netlink_ctx *ctx, unsigned int flags, + goto cache_fails; + } + if (flags & NFT_CACHE_OBJECT_BIT) { +- obj_list = obj_cache_dump(ctx, table); +- if (!obj_list) { +- ret = -1; +- goto cache_fails; +- } + ret = obj_cache_init(ctx, table, obj_list); +- +- nftnl_obj_list_free(obj_list); +- + if (ret < 0) + goto cache_fails; + } +@@ -1148,6 +1220,8 @@ static int cache_init_objects(struct netlink_ctx *ctx, unsigned int flags, + cache_fails: + if (set_list) + nftnl_set_list_free(set_list); ++ if (obj_list) ++ nftnl_obj_list_free(obj_list); + if (ft_list) + nftnl_flowtable_list_free(ft_list); + diff --git a/0121-cache-consolidate-reset-command.patch b/0121-cache-consolidate-reset-command.patch new file mode 100644 index 0000000..b206efa --- /dev/null +++ b/0121-cache-consolidate-reset-command.patch @@ -0,0 +1,528 @@ +From 7677fd4e2273487c86f5f00eed92bcaa0ae03ddc Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:10:09 +0200 +Subject: [PATCH] cache: consolidate reset command + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit dbff26bfba8336c485a270509440e94bc1240d08 + +commit dbff26bfba8336c485a270509440e94bc1240d08 +Author: Pablo Neira Ayuso +Date: Mon Aug 26 00:41:42 2024 +0200 + + cache: consolidate reset command + + Reset command does not utilize the cache infrastructure. + + This implicitly fixes a crash with anonymous sets because elements are + not fetched. I initially tried to fix it by toggling the missing cache + flags, but then ASAN reports memleaks. + + To address these issues relies on Phil's list filtering infrastructure + which updates is expanded to accomodate filtering requirements of the + reset commands, such as 'reset table ip' where only the family is sent + to the kernel. + + After this update, tests/shell reports a few inconsistencies between + reset and list commands: + + - reset rules chain t c2 + + display sets, but it should only list the given chain. + + - reset rules table t + reset rules ip + + do not list elements in the set. In both cases, these are fully + listing a given table and family, elements should be included. + + The consolidation also ensures list and reset will not differ. + + A few more notes: + + - CMD_OBJ_TABLE is used for: + + rules family table + + from the parser, due to the lack of a better enum, same applies to + CMD_OBJ_CHAIN. + + - CMD_OBJ_ELEMENTS still does not use the cache, but same occurs in + the CMD_GET command case which needs to be consolidated. + + Closes: https://bugzilla.netfilter.org/show_bug.cgi?id=1763 + Fixes: 83e0f4402fb7 ("Implement 'reset {set,map,element}' commands") + Fixes: 1694df2de79f ("Implement 'reset rule' and 'reset rules' commands") + Tested-by: Eric Garver + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + include/cache.h | 10 ++- + include/netlink.h | 5 -- + src/cache.c | 81 +++++++++++++------ + src/evaluate.c | 2 + + src/mnl.c | 7 +- + src/netlink.c | 78 ------------------ + src/parser_bison.y | 8 +- + src/rule.c | 48 +---------- + .../testcases/rule_management/0011reset_0 | 10 +-- + 9 files changed, 78 insertions(+), 171 deletions(-) + +diff --git a/include/cache.h b/include/cache.h +index e47dc88..4cf7d31 100644 +--- a/include/cache.h ++++ b/include/cache.h +@@ -65,6 +65,12 @@ struct nft_cache_filter { + struct { + struct list_head head; + } obj[NFT_CACHE_HSIZE]; ++ ++ struct { ++ bool obj; ++ bool rule; ++ bool elem; ++ } reset; + }; + + struct nft_cache; +@@ -150,8 +156,4 @@ struct netlink_ctx; + void nft_chain_cache_update(struct netlink_ctx *ctx, struct table *table, + const char *chain); + +-int rule_cache_dump(struct netlink_ctx *ctx, const struct handle *h, +- const struct nft_cache_filter *filter, +- bool dump, bool reset); +- + #endif /* _NFT_CACHE_H_ */ +diff --git a/include/netlink.h b/include/netlink.h +index 2ce4b39..80e18ee 100644 +--- a/include/netlink.h ++++ b/include/netlink.h +@@ -176,8 +176,6 @@ extern int netlink_delinearize_setelem(struct nftnl_set_elem *nlse, + struct nft_cache *cache); + + extern int netlink_list_objs(struct netlink_ctx *ctx, const struct handle *h); +-extern int netlink_reset_objs(struct netlink_ctx *ctx, const struct cmd *cmd, +- uint32_t type, bool dump); + extern struct obj *netlink_delinearize_obj(struct netlink_ctx *ctx, + struct nftnl_obj *nlo); + +@@ -186,9 +184,6 @@ extern int netlink_list_flowtables(struct netlink_ctx *ctx, + extern struct flowtable *netlink_delinearize_flowtable(struct netlink_ctx *ctx, + struct nftnl_flowtable *nlo); + +-extern int netlink_reset_rules(struct netlink_ctx *ctx, const struct cmd *cmd, +- bool dump); +- + extern void netlink_dump_chain(const struct nftnl_chain *nlc, + struct netlink_ctx *ctx); + extern void netlink_dump_rule(const struct nftnl_rule *nlr, +diff --git a/src/cache.c b/src/cache.c +index 2407fc2..77a67f7 100644 +--- a/src/cache.c ++++ b/src/cache.c +@@ -321,27 +321,49 @@ static unsigned int evaluate_cache_list(struct nft_ctx *nft, struct cmd *cmd, + static unsigned int evaluate_cache_reset(struct cmd *cmd, unsigned int flags, + struct nft_cache_filter *filter) + { ++ assert(filter); ++ + switch (cmd->obj) { ++ case CMD_OBJ_TABLE: ++ case CMD_OBJ_CHAIN: + case CMD_OBJ_RULES: + case CMD_OBJ_RULE: +- if (filter) { +- if (cmd->handle.table.name) { +- filter->list.family = cmd->handle.family; +- filter->list.table = cmd->handle.table.name; +- } +- if (cmd->handle.chain.name) +- filter->list.chain = cmd->handle.chain.name; ++ if (cmd->handle.table.name) { ++ filter->list.family = cmd->handle.family; ++ filter->list.table = cmd->handle.table.name; + } +- flags |= NFT_CACHE_SET | NFT_CACHE_FLOWTABLE | +- NFT_CACHE_OBJECT | NFT_CACHE_CHAIN; ++ if (cmd->handle.chain.name) ++ filter->list.chain = cmd->handle.chain.name; ++ if (cmd->handle.family) ++ filter->list.family = cmd->handle.family; ++ if (cmd->handle.handle.id) ++ filter->list.rule_handle = cmd->handle.handle.id; ++ ++ filter->reset.rule = true; ++ flags |= NFT_CACHE_FULL; ++ break; ++ case CMD_OBJ_COUNTER: ++ case CMD_OBJ_COUNTERS: ++ obj_filter_setup(cmd, &flags, filter, NFT_OBJECT_COUNTER); ++ filter->reset.obj = true; ++ break; ++ case CMD_OBJ_QUOTA: ++ case CMD_OBJ_QUOTAS: ++ obj_filter_setup(cmd, &flags, filter, NFT_OBJECT_QUOTA); ++ filter->reset.obj = true; + break; +- case CMD_OBJ_ELEMENTS: + case CMD_OBJ_SET: + case CMD_OBJ_MAP: +- flags |= NFT_CACHE_SET; ++ if (cmd->handle.table.name && cmd->handle.set.name) { ++ filter->list.family = cmd->handle.family; ++ filter->list.table = cmd->handle.table.name; ++ filter->list.set = cmd->handle.set.name; ++ } ++ flags |= NFT_CACHE_SETELEM; ++ filter->reset.elem = true; + break; + default: +- flags |= NFT_CACHE_TABLE; ++ flags |= NFT_CACHE_FULL; + break; + } + flags |= NFT_CACHE_REFRESH; +@@ -457,6 +479,7 @@ err_name_too_long: + static void reset_filter(struct nft_cache_filter *filter) + { + memset(&filter->list, 0, sizeof(filter->list)); ++ memset(&filter->reset, 0, sizeof(filter->reset)); + } + + int nft_cache_evaluate(struct nft_ctx *nft, struct list_head *cmds, +@@ -700,23 +723,32 @@ static int list_rule_cb(struct nftnl_rule *nlr, void *data) + return 0; + } + +-int rule_cache_dump(struct netlink_ctx *ctx, const struct handle *h, +- const struct nft_cache_filter *filter, +- bool dump, bool reset) ++static int rule_cache_dump(struct netlink_ctx *ctx, const struct handle *h, ++ const struct nft_cache_filter *filter) + { + struct nftnl_rule_list *rule_cache; + const char *table = h->table.name; + const char *chain = NULL; + uint64_t rule_handle = 0; ++ int family = h->family; ++ bool dump = true; + + if (filter) { +- table = filter->list.table; +- chain = filter->list.chain; +- rule_handle = filter->list.rule_handle; ++ if (filter->list.table) ++ table = filter->list.table; ++ if (filter->list.chain) ++ chain = filter->list.chain; ++ if (filter->list.rule_handle) { ++ rule_handle = filter->list.rule_handle; ++ dump = false; ++ } ++ if (filter->list.family) ++ family = filter->list.family; + } + +- rule_cache = mnl_nft_rule_dump(ctx, h->family, +- table, chain, rule_handle, dump, reset); ++ rule_cache = mnl_nft_rule_dump(ctx, family, ++ table, chain, rule_handle, dump, ++ filter->reset.rule); + if (rule_cache == NULL) { + if (errno == EINTR) + return -1; +@@ -900,7 +932,8 @@ static struct nftnl_obj_list *obj_cache_dump(struct netlink_ctx *ctx, + if (filter->list.obj_type) + type = filter->list.obj_type; + } +- obj_list = mnl_nft_obj_dump(ctx, family, table, obj, type, dump, false); ++ obj_list = mnl_nft_obj_dump(ctx, family, table, obj, type, dump, ++ filter->reset.obj); + if (!obj_list) { + if (errno == EINTR) + return NULL; +@@ -1074,7 +1107,7 @@ static int rule_init_cache(struct netlink_ctx *ctx, struct table *table, + struct chain *chain; + int ret; + +- ret = rule_cache_dump(ctx, &table->handle, filter, true, false); ++ ret = rule_cache_dump(ctx, &table->handle, filter); + + list_for_each_entry_safe(rule, nrule, &ctx->list, list) { + chain = chain_cache_find(table, rule->handle.chain.name); +@@ -1170,7 +1203,7 @@ static int cache_init_objects(struct netlink_ctx *ctx, unsigned int flags, + continue; + + ret = netlink_list_setelems(ctx, &set->handle, +- set, false); ++ set, filter->reset.elem); + if (ret < 0) + goto cache_fails; + } +@@ -1183,7 +1216,7 @@ static int cache_init_objects(struct netlink_ctx *ctx, unsigned int flags, + continue; + + ret = netlink_list_setelems(ctx, &set->handle, +- set, false); ++ set, filter->reset.elem); + if (ret < 0) + goto cache_fails; + } +diff --git a/src/evaluate.c b/src/evaluate.c +index af811a7..32d4c75 100644 +--- a/src/evaluate.c ++++ b/src/evaluate.c +@@ -5669,6 +5669,8 @@ static int cmd_evaluate_reset(struct eval_ctx *ctx, struct cmd *cmd) + return 0; + case CMD_OBJ_ELEMENTS: + return setelem_evaluate(ctx, cmd); ++ case CMD_OBJ_TABLE: ++ case CMD_OBJ_CHAIN: + case CMD_OBJ_SET: + case CMD_OBJ_MAP: + return cmd_evaluate_list(ctx, cmd); +diff --git a/src/mnl.c b/src/mnl.c +index 9e4bfcd..9bddf10 100644 +--- a/src/mnl.c ++++ b/src/mnl.c +@@ -1161,8 +1161,11 @@ struct nftnl_table_list *mnl_nft_table_dump(struct netlink_ctx *ctx, + if (!nlt) + memory_allocation_error(); + +- nftnl_table_set_u32(nlt, NFTNL_TABLE_FAMILY, family); +- nftnl_table_set_str(nlt, NFTNL_TABLE_NAME, table); ++ if (family != NFPROTO_UNSPEC) ++ nftnl_table_set_u32(nlt, NFTNL_TABLE_FAMILY, family); ++ if (table) ++ nftnl_table_set_str(nlt, NFTNL_TABLE_NAME, table); ++ + flags = NLM_F_ACK; + } + +diff --git a/src/netlink.c b/src/netlink.c +index 72f4ba4..0caeeff 100644 +--- a/src/netlink.c ++++ b/src/netlink.c +@@ -1755,84 +1755,6 @@ void netlink_dump_flowtable(struct nftnl_flowtable *flo, + fprintf(fp, "\n"); + } + +-static int list_obj_cb(struct nftnl_obj *nls, void *arg) +-{ +- struct netlink_ctx *ctx = arg; +- struct obj *obj; +- +- obj = netlink_delinearize_obj(ctx, nls); +- if (obj == NULL) +- return -1; +- list_add_tail(&obj->list, &ctx->list); +- return 0; +-} +- +-int netlink_reset_objs(struct netlink_ctx *ctx, const struct cmd *cmd, +- uint32_t type, bool dump) +-{ +- const struct handle *h = &cmd->handle; +- struct nftnl_obj_list *obj_cache; +- int err; +- +- obj_cache = mnl_nft_obj_dump(ctx, h->family, +- h->table.name, h->obj.name, type, dump, true); +- if (obj_cache == NULL) +- return -1; +- +- err = nftnl_obj_list_foreach(obj_cache, list_obj_cb, ctx); +- nftnl_obj_list_free(obj_cache); +- return err; +-} +- +-int netlink_reset_rules(struct netlink_ctx *ctx, const struct cmd *cmd, +- bool dump) +-{ +- const struct handle *h = &cmd->handle; +- struct nft_cache_filter f = { +- .list.table = h->table.name, +- .list.chain = h->chain.name, +- .list.rule_handle = h->handle.id, +- }; +- struct rule *rule, *next, *crule, *cnext; +- struct table *table; +- struct chain *chain; +- int ret; +- +- ret = rule_cache_dump(ctx, h, &f, dump, true); +- +- list_for_each_entry_safe(rule, next, &ctx->list, list) { +- table = table_cache_find(&ctx->nft->cache.table_cache, +- rule->handle.table.name, +- rule->handle.family); +- if (!table) +- continue; +- +- chain = chain_cache_find(table, rule->handle.chain.name); +- if (!chain) +- continue; +- +- list_del(&rule->list); +- list_for_each_entry_safe(crule, cnext, &chain->rules, list) { +- if (crule->handle.handle.id != rule->handle.handle.id) +- continue; +- +- list_replace(&crule->list, &rule->list); +- rule_free(crule); +- rule = NULL; +- break; +- } +- if (rule) { +- list_add_tail(&rule->list, &chain->rules); +- } +- } +- list_for_each_entry_safe(rule, next, &ctx->list, list) { +- list_del(&rule->list); +- rule_free(rule); +- } +- +- return ret; +-} +- + struct flowtable * + netlink_delinearize_flowtable(struct netlink_ctx *ctx, + struct nftnl_flowtable *nlo) +diff --git a/src/parser_bison.y b/src/parser_bison.y +index c18e739..25a285c 100644 +--- a/src/parser_bison.y ++++ b/src/parser_bison.y +@@ -1753,21 +1753,21 @@ reset_cmd : COUNTERS ruleset_spec + } + | RULES table_spec + { +- $$ = cmd_alloc(CMD_RESET, CMD_OBJ_RULES, &$2, &@$, NULL); ++ $$ = cmd_alloc(CMD_RESET, CMD_OBJ_TABLE, &$2, &@$, NULL); + } + | RULES TABLE table_spec + { + /* alias of previous rule. */ +- $$ = cmd_alloc(CMD_RESET, CMD_OBJ_RULES, &$3, &@$, NULL); ++ $$ = cmd_alloc(CMD_RESET, CMD_OBJ_TABLE, &$3, &@$, NULL); + } + | RULES chain_spec + { +- $$ = cmd_alloc(CMD_RESET, CMD_OBJ_RULES, &$2, &@$, NULL); ++ $$ = cmd_alloc(CMD_RESET, CMD_OBJ_CHAIN, &$2, &@$, NULL); + } + | RULES CHAIN chain_spec + { + /* alias of previous rule. */ +- $$ = cmd_alloc(CMD_RESET, CMD_OBJ_RULES, &$3, &@$, NULL); ++ $$ = cmd_alloc(CMD_RESET, CMD_OBJ_CHAIN, &$3, &@$, NULL); + } + | RULE ruleid_spec + { +diff --git a/src/rule.c b/src/rule.c +index 89101f9..683e69b 100644 +--- a/src/rule.c ++++ b/src/rule.c +@@ -2463,58 +2463,12 @@ static int do_command_get(struct netlink_ctx *ctx, struct cmd *cmd) + + static int do_command_reset(struct netlink_ctx *ctx, struct cmd *cmd) + { +- struct obj *obj, *next; +- struct table *table; +- bool dump = false; +- uint32_t type; +- int ret; +- + switch (cmd->obj) { +- case CMD_OBJ_COUNTERS: +- dump = true; +- /* fall through */ +- case CMD_OBJ_COUNTER: +- type = NFT_OBJECT_COUNTER; +- break; +- case CMD_OBJ_QUOTAS: +- dump = true; +- /* fall through */ +- case CMD_OBJ_QUOTA: +- type = NFT_OBJECT_QUOTA; +- break; +- case CMD_OBJ_RULES: +- ret = netlink_reset_rules(ctx, cmd, true); +- if (ret < 0) +- return ret; +- +- return do_command_list(ctx, cmd); +- case CMD_OBJ_RULE: +- return netlink_reset_rules(ctx, cmd, false); + case CMD_OBJ_ELEMENTS: + return do_get_setelems(ctx, cmd, true); +- case CMD_OBJ_SET: +- case CMD_OBJ_MAP: +- ret = netlink_list_setelems(ctx, &cmd->handle, cmd->set, true); +- if (ret < 0) +- return ret; +- +- return do_command_list(ctx, cmd); + default: +- BUG("invalid command object type %u\n", cmd->obj); +- } +- +- ret = netlink_reset_objs(ctx, cmd, type, dump); +- list_for_each_entry_safe(obj, next, &ctx->list, list) { +- table = table_cache_find(&ctx->nft->cache.table_cache, +- obj->handle.table.name, +- obj->handle.family); +- if (!obj_cache_find(table, obj->handle.obj.name, obj->type)) { +- list_del(&obj->list); +- obj_cache_add(obj, table); +- } ++ break; + } +- if (ret < 0) +- return ret; + + return do_command_list(ctx, cmd); + } +diff --git a/tests/shell/testcases/rule_management/0011reset_0 b/tests/shell/testcases/rule_management/0011reset_0 +index 33eadd9..3fede56 100755 +--- a/tests/shell/testcases/rule_management/0011reset_0 ++++ b/tests/shell/testcases/rule_management/0011reset_0 +@@ -74,13 +74,6 @@ $DIFF -u <(echo "$EXPECT") <($NFT list ruleset) + + echo "resetting specific chain" + EXPECT='table ip t { +- set s { +- type ipv4_addr +- size 65535 +- flags dynamic +- counter +- } +- + chain c2 { + counter packets 3 bytes 13 accept + counter packets 4 bytes 14 drop +@@ -95,6 +88,7 @@ EXPECT='table ip t { + size 65535 + flags dynamic + counter ++ elements = { 1.1.1.1 counter packets 1 bytes 11 } + } + + chain c { +@@ -116,6 +110,7 @@ EXPECT='table ip t { + size 65535 + flags dynamic + counter ++ elements = { 1.1.1.1 counter packets 1 bytes 11 } + } + + chain c { +@@ -143,6 +138,7 @@ EXPECT='table ip t { + size 65535 + flags dynamic + counter ++ elements = { 1.1.1.1 counter packets 1 bytes 11 } + } + + chain c { diff --git a/0122-cache-assert-filter-when-calling-nft_cache_evaluate.patch b/0122-cache-assert-filter-when-calling-nft_cache_evaluate.patch new file mode 100644 index 0000000..ab9c28e --- /dev/null +++ b/0122-cache-assert-filter-when-calling-nft_cache_evaluate.patch @@ -0,0 +1,95 @@ +From 13898075b80145cc92cd2c330dd2461c264e795d Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:10:09 +0200 +Subject: [PATCH] cache: assert filter when calling nft_cache_evaluate() + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 4dd20f3bbd606eed4869ebe449debee8b2ac7900 + +commit 4dd20f3bbd606eed4869ebe449debee8b2ac7900 +Author: Pablo Neira Ayuso +Date: Thu Aug 29 12:42:08 2024 +0200 + + cache: assert filter when calling nft_cache_evaluate() + + nft_cache_evaluate() always takes a non-null filter, remove superfluous + checks when calculating cache requirements via flags. + + Note that filter is still option from netlink dump path, since this can + be called from error path to provide hints. + + Fixes: 08725a9dc14c ("cache: filter out rules by chain") + Fixes: b3ed8fd8c9f3 ("cache: missing family in cache filtering") + Fixes: 635ee1cad8aa ("cache: filter out sets and maps that are not requested") + Fixes: 3f1d3912c3a6 ("cache: filter out tables that are not requested") + Tested-by: Eric Garver + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/cache.c | 16 +++++++--------- + 1 file changed, 7 insertions(+), 9 deletions(-) + +diff --git a/src/cache.c b/src/cache.c +index 77a67f7..82553a9 100644 +--- a/src/cache.c ++++ b/src/cache.c +@@ -219,18 +219,17 @@ static unsigned int evaluate_cache_list(struct nft_ctx *nft, struct cmd *cmd, + { + switch (cmd->obj) { + case CMD_OBJ_TABLE: +- if (filter) +- filter->list.family = cmd->handle.family; ++ filter->list.family = cmd->handle.family; + if (!cmd->handle.table.name) { + flags |= NFT_CACHE_TABLE; + break; +- } else if (filter) { ++ } else { + filter->list.table = cmd->handle.table.name; + } + flags |= NFT_CACHE_FULL; + break; + case CMD_OBJ_CHAIN: +- if (filter && cmd->handle.chain.name) { ++ if (cmd->handle.chain.name) { + filter->list.family = cmd->handle.family; + filter->list.table = cmd->handle.table.name; + filter->list.chain = cmd->handle.chain.name; +@@ -243,7 +242,7 @@ static unsigned int evaluate_cache_list(struct nft_ctx *nft, struct cmd *cmd, + break; + case CMD_OBJ_SET: + case CMD_OBJ_MAP: +- if (filter && cmd->handle.table.name && cmd->handle.set.name) { ++ if (cmd->handle.table.name && cmd->handle.set.name) { + filter->list.family = cmd->handle.family; + filter->list.table = cmd->handle.table.name; + filter->list.set = cmd->handle.set.name; +@@ -263,8 +262,7 @@ static unsigned int evaluate_cache_list(struct nft_ctx *nft, struct cmd *cmd, + flags |= NFT_CACHE_SETELEM; + break; + case CMD_OBJ_FLOWTABLE: +- if (filter && +- cmd->handle.table.name && ++ if (cmd->handle.table.name && + cmd->handle.flowtable.name) { + filter->list.family = cmd->handle.family; + filter->list.table = cmd->handle.table.name; +@@ -321,8 +319,6 @@ static unsigned int evaluate_cache_list(struct nft_ctx *nft, struct cmd *cmd, + static unsigned int evaluate_cache_reset(struct cmd *cmd, unsigned int flags, + struct nft_cache_filter *filter) + { +- assert(filter); +- + switch (cmd->obj) { + case CMD_OBJ_TABLE: + case CMD_OBJ_CHAIN: +@@ -489,6 +485,8 @@ int nft_cache_evaluate(struct nft_ctx *nft, struct list_head *cmds, + unsigned int flags, batch_flags = NFT_CACHE_EMPTY; + struct cmd *cmd; + ++ assert(filter); ++ + list_for_each_entry(cmd, cmds, list) { + if (nft_handle_validate(cmd, msgs) < 0) + return -1; diff --git a/0123-cache-remove-full-cache-requirement-when-echo-flag-i.patch b/0123-cache-remove-full-cache-requirement-when-echo-flag-i.patch new file mode 100644 index 0000000..d725f16 --- /dev/null +++ b/0123-cache-remove-full-cache-requirement-when-echo-flag-i.patch @@ -0,0 +1,39 @@ +From e08be9cb13b49c4b8e6c2eb8ed50f70abb362a39 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:10:09 +0200 +Subject: [PATCH] cache: remove full cache requirement when echo flag is set on + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 53a503ad4a1abfa0374b3d12e884b69dc6df4b4f + +commit 53a503ad4a1abfa0374b3d12e884b69dc6df4b4f +Author: Pablo Neira Ayuso +Date: Thu Aug 29 12:42:12 2024 +0200 + + cache: remove full cache requirement when echo flag is set on + + The echo flag does not use the cache infrastructure yet, it relies on + the monitor cache which follows the netlink_echo_callback() path. + + Fixes: 01e5c6f0ed03 ("src: add cache level flags") + Tested-by: Eric Garver + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/cache.c | 2 -- + 1 file changed, 2 deletions(-) + +diff --git a/src/cache.c b/src/cache.c +index 82553a9..450be45 100644 +--- a/src/cache.c ++++ b/src/cache.c +@@ -499,8 +499,6 @@ int nft_cache_evaluate(struct nft_ctx *nft, struct list_head *cmds, + case CMD_INSERT: + case CMD_CREATE: + flags = evaluate_cache_add(cmd, flags); +- if (nft_output_echo(&nft->output)) +- flags |= NFT_CACHE_FULL; + break; + case CMD_REPLACE: + flags = NFT_CACHE_FULL; diff --git a/0124-cache-relax-requirement-for-replace-rule-command.patch b/0124-cache-relax-requirement-for-replace-rule-command.patch new file mode 100644 index 0000000..62ee213 --- /dev/null +++ b/0124-cache-relax-requirement-for-replace-rule-command.patch @@ -0,0 +1,85 @@ +From 2e6514f3b31f6c7e66d94a3fa77dcc707c09175c Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:10:09 +0200 +Subject: [PATCH] cache: relax requirement for replace rule command + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 4984da8cc427974ea63796fa60a791b714a71440 +Conflicts: Dropped changes to non-existent .json-nft dump + +commit 4984da8cc427974ea63796fa60a791b714a71440 +Author: Pablo Neira Ayuso +Date: Thu Aug 29 12:42:14 2024 +0200 + + cache: relax requirement for replace rule command + + No need for full cache, this command relies on the rule handle which is + not validated from userspace. Cache requirements are similar to those + of add/create/delete rule commands. + + This speeds up incremental updates with large rulesets. + + Extend tests/coverage for rule replacement. + + Fixes: 01e5c6f0ed03 ("src: add cache level flags") + Tested-by: Eric Garver + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/cache.c | 4 ++-- + tests/shell/testcases/rule_management/0004replace_0 | 8 ++++++-- + .../testcases/rule_management/dumps/0004replace_0.nft | 11 ++++++++++- + 3 files changed, 18 insertions(+), 5 deletions(-) + +diff --git a/src/cache.c b/src/cache.c +index 450be45..12286b3 100644 +--- a/src/cache.c ++++ b/src/cache.c +@@ -500,8 +500,8 @@ int nft_cache_evaluate(struct nft_ctx *nft, struct list_head *cmds, + case CMD_CREATE: + flags = evaluate_cache_add(cmd, flags); + break; +- case CMD_REPLACE: +- flags = NFT_CACHE_FULL; ++ case CMD_REPLACE: /* only for rule */ ++ flags = NFT_CACHE_TABLE | NFT_CACHE_SET; + break; + case CMD_DELETE: + case CMD_DESTROY: +diff --git a/tests/shell/testcases/rule_management/0004replace_0 b/tests/shell/testcases/rule_management/0004replace_0 +index c3329af..18dc4a9 100755 +--- a/tests/shell/testcases/rule_management/0004replace_0 ++++ b/tests/shell/testcases/rule_management/0004replace_0 +@@ -6,5 +6,9 @@ + set -e + $NFT add table t + $NFT add chain t c +-$NFT add rule t c accept # should have handle 2 +-$NFT replace rule t c handle 2 drop ++$NFT 'add set t s1 { type ipv4_addr; }' ++$NFT 'add set t s2 { type ipv4_addr; flags interval; }' ++$NFT add rule t c accept # should have handle 4 ++$NFT replace rule t c handle 4 drop ++$NFT replace rule t c handle 4 ip saddr { 1.1.1.1, 2.2.2.2 } ++$NFT replace rule t c handle 4 ip saddr @s2 ip daddr { 3.3.3.3, 4.4.4.4 } +diff --git a/tests/shell/testcases/rule_management/dumps/0004replace_0.nft b/tests/shell/testcases/rule_management/dumps/0004replace_0.nft +index e20952e..803c0de 100644 +--- a/tests/shell/testcases/rule_management/dumps/0004replace_0.nft ++++ b/tests/shell/testcases/rule_management/dumps/0004replace_0.nft +@@ -1,5 +1,14 @@ + table ip t { ++ set s1 { ++ type ipv4_addr ++ } ++ ++ set s2 { ++ type ipv4_addr ++ flags interval ++ } ++ + chain c { +- drop ++ ip saddr @s2 ip daddr { 3.3.3.3, 4.4.4.4 } + } + } diff --git a/0125-cache-position-does-not-require-full-cache.patch b/0125-cache-position-does-not-require-full-cache.patch new file mode 100644 index 0000000..d58cc37 --- /dev/null +++ b/0125-cache-position-does-not-require-full-cache.patch @@ -0,0 +1,44 @@ +From c984bf1e1f071f237da349cb1d566ac7d0c3fa7a Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:10:25 +0200 +Subject: [PATCH] cache: position does not require full cache + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit d414f756af9d638fe0c0002b2df31c8c17a15002 + +commit d414f756af9d638fe0c0002b2df31c8c17a15002 +Author: Pablo Neira Ayuso +Date: Thu Aug 29 12:42:17 2024 +0200 + + cache: position does not require full cache + + position refers to the rule handle, it has similar cache requirements as + replace rule command, relax cache requirements. + + Commit e5382c0d08e3 ("src: Support intra-transaction rule references") + uses position.id for index support which requires a full cache, but + only in such case. + + Fixes: 01e5c6f0ed03 ("src: add cache level flags") + Tested-by: Eric Garver + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/cache.c | 3 +-- + 1 file changed, 1 insertion(+), 2 deletions(-) + +diff --git a/src/cache.c b/src/cache.c +index 12286b3..41c5a6f 100644 +--- a/src/cache.c ++++ b/src/cache.c +@@ -66,8 +66,7 @@ static unsigned int evaluate_cache_add(struct cmd *cmd, unsigned int flags) + NFT_CACHE_OBJECT | + NFT_CACHE_FLOWTABLE; + +- if (cmd->handle.index.id || +- cmd->handle.position.id) ++ if (cmd->handle.index.id) + flags |= NFT_CACHE_FULL | NFT_CACHE_UPDATE; + break; + default: diff --git a/0126-libnftables-Zero-ctx-vars-after-freeing-it.patch b/0126-libnftables-Zero-ctx-vars-after-freeing-it.patch new file mode 100644 index 0000000..01fe191 --- /dev/null +++ b/0126-libnftables-Zero-ctx-vars-after-freeing-it.patch @@ -0,0 +1,40 @@ +From 5b5efaa25f056785bf16dff019766e7008330485 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:10:25 +0200 +Subject: [PATCH] libnftables: Zero ctx->vars after freeing it + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit d361be1f8734461e27117f6c569acf2189fcf81e + +commit d361be1f8734461e27117f6c569acf2189fcf81e +Author: Phil Sutter +Date: Tue Sep 3 17:43:19 2024 +0200 + + libnftables: Zero ctx->vars after freeing it + + Leaving the invalid pointer value in place will cause a double-free when + users call nft_ctx_clear_vars() first, then nft_ctx_free(). Moreover, + nft_ctx_add_var() passes the pointer to mrealloc() and thus assumes it + to be either NULL or valid. + + Closes: https://bugzilla.netfilter.org/show_bug.cgi?id=1772 + Fixes: 9edaa6a51eab4 ("src: add --define key=value") + Signed-off-by: Phil Sutter + +Signed-off-by: Phil Sutter +--- + src/libnftables.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/src/libnftables.c b/src/libnftables.c +index 02570c4..b6bbece 100644 +--- a/src/libnftables.c ++++ b/src/libnftables.c +@@ -159,6 +159,7 @@ void nft_ctx_clear_vars(struct nft_ctx *ctx) + } + ctx->num_vars = 0; + free(ctx->vars); ++ ctx->vars = NULL; + } + + EXPORT_SYMBOL(nft_ctx_add_include_path); diff --git a/0127-tests-shell-stabilize-packetpath-payload.patch b/0127-tests-shell-stabilize-packetpath-payload.patch new file mode 100644 index 0000000..49cb6b2 --- /dev/null +++ b/0127-tests-shell-stabilize-packetpath-payload.patch @@ -0,0 +1,135 @@ +From 0b2841cac29c03344d9e83c2c43903f275417c8e Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:10:25 +0200 +Subject: [PATCH] tests: shell: stabilize packetpath/payload + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit a1365628eaa73199aab32bda06c9ddd446613849 + +commit a1365628eaa73199aab32bda06c9ddd446613849 +Author: Pablo Neira Ayuso +Date: Mon Sep 9 12:47:35 2024 +0200 + + tests: shell: stabilize packetpath/payload + + - Add sleep calls after setting up container topology. + - Extend TCP connect timeout to 4 seconds. Test has no listener, this is + just sending SYN packets that are rejected but it works to test the + payload mangling ruleset. + - fix incorrect logic to check for 0 matching packets through grep. + + Fixes: 84da729e067a ("tests: shell: add test to cover payload transport match and mangle") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + tests/shell/testcases/packetpath/payload | 64 +++++++++++++----------- + 1 file changed, 34 insertions(+), 30 deletions(-) + +diff --git a/tests/shell/testcases/packetpath/payload b/tests/shell/testcases/packetpath/payload +index 4c5c42d..1fb86aa 100755 +--- a/tests/shell/testcases/packetpath/payload ++++ b/tests/shell/testcases/packetpath/payload +@@ -42,6 +42,8 @@ run_test() + ip -net "$ns1" addr add $ns1_addr/$cidr dev veth0 + ip -net "$ns2" addr add $ns2_addr/$cidr dev veth0 + ++ sleep 3 ++ + RULESET="table netdev payload_netdev { + counter ingress {} + counter egress {} +@@ -90,33 +92,33 @@ table inet payload_inet { + + ip netns exec "$ns1" $NFT -f - <<< "$RULESET" || exit 1 + +- ip netns exec "$ns1" socat -u STDIN TCP:$nsx2_addr:8887,connect-timeout=2 < /dev/null > /dev/null +- ip netns exec "$ns1" socat -u STDIN TCP:$nsx2_addr:8888,connect-timeout=2 < /dev/null > /dev/null ++ ip netns exec "$ns1" socat -u STDIN TCP:$nsx2_addr:8887,connect-timeout=4 < /dev/null > /dev/null ++ ip netns exec "$ns1" socat -u STDIN TCP:$nsx2_addr:8888,connect-timeout=4 < /dev/null > /dev/null + +- ip netns exec "$ns1" socat -u STDIN TCP:$nsx2_addr:8880,connect-timeout=2 < /dev/null > /dev/null +- ip netns exec "$ns1" socat -u STDIN TCP:$nsx2_addr:8881,connect-timeout=2 < /dev/null > /dev/null ++ ip netns exec "$ns1" socat -u STDIN TCP:$nsx2_addr:8880,connect-timeout=4 < /dev/null > /dev/null ++ ip netns exec "$ns1" socat -u STDIN TCP:$nsx2_addr:8881,connect-timeout=4 < /dev/null > /dev/null + +- ip netns exec "$ns2" socat -u STDIN TCP:$nsx1_addr:7777,connect-timeout=2 < /dev/null > /dev/null +- ip netns exec "$ns2" socat -u STDIN TCP:$nsx1_addr:7778,connect-timeout=2 < /dev/null > /dev/null ++ ip netns exec "$ns2" socat -u STDIN TCP:$nsx1_addr:7777,connect-timeout=4 < /dev/null > /dev/null ++ ip netns exec "$ns2" socat -u STDIN TCP:$nsx1_addr:7778,connect-timeout=4 < /dev/null > /dev/null + +- ip netns exec "$ns2" socat -u STDIN TCP:$nsx1_addr:7770,connect-timeout=2 < /dev/null > /dev/null +- ip netns exec "$ns2" socat -u STDIN TCP:$nsx1_addr:7771,connect-timeout=2 < /dev/null > /dev/null ++ ip netns exec "$ns2" socat -u STDIN TCP:$nsx1_addr:7770,connect-timeout=4 < /dev/null > /dev/null ++ ip netns exec "$ns2" socat -u STDIN TCP:$nsx1_addr:7771,connect-timeout=4 < /dev/null > /dev/null + + ip netns exec "$ns1" $NFT list ruleset + +- ip netns exec "$ns1" $NFT list counter netdev payload_netdev ingress | grep -v "packets 0" > /dev/null || exit 1 +- ip netns exec "$ns1" $NFT list counter netdev payload_netdev mangle_ingress | grep -v "packets 0" > /dev/null || exit 1 +- ip netns exec "$ns1" $NFT list counter netdev payload_netdev mangle_ingress_match | grep -v "packets 0" > /dev/null || exit 1 +- ip netns exec "$ns1" $NFT list counter netdev payload_netdev egress | grep -v "packets 0" > /dev/null || exit 1 +- ip netns exec "$ns1" $NFT list counter netdev payload_netdev mangle_egress | grep -v "packets 0" > /dev/null || exit 1 +- ip netns exec "$ns1" $NFT list counter netdev payload_netdev mangle_egress_match | grep -v "packets 0" > /dev/null || exit 1 ++ ip netns exec "$ns1" $NFT list counter netdev payload_netdev ingress | grep -q "packets 0" && exit 1 ++ ip netns exec "$ns1" $NFT list counter netdev payload_netdev mangle_ingress | grep -q "packets 0" && exit 1 ++ ip netns exec "$ns1" $NFT list counter netdev payload_netdev mangle_ingress_match | grep -q "packets 0" && exit 1 ++ ip netns exec "$ns1" $NFT list counter netdev payload_netdev egress | grep -q "packets 0" && exit 1 ++ ip netns exec "$ns1" $NFT list counter netdev payload_netdev mangle_egress | grep -q "packets 0" && exit 1 ++ ip netns exec "$ns1" $NFT list counter netdev payload_netdev mangle_egress_match | grep -q "packets 0" && exit 1 + +- ip netns exec "$ns1" $NFT list counter inet payload_inet input | grep -v "packets 0" > /dev/null || exit 1 +- ip netns exec "$ns1" $NFT list counter inet payload_inet mangle_input | grep -v "packets 0" > /dev/null || exit 1 +- ip netns exec "$ns1" $NFT list counter inet payload_inet mangle_input_match | grep -v "packets 0" > /dev/null || exit 1 +- ip netns exec "$ns1" $NFT list counter inet payload_inet output | grep -v "packets 0" > /dev/null || exit 1 +- ip netns exec "$ns1" $NFT list counter inet payload_inet mangle_output | grep -v "packets 0" > /dev/null || exit 1 +- ip netns exec "$ns1" $NFT list counter inet payload_inet mangle_output_match | grep -v "packets 0" > /dev/null || exit 1 ++ ip netns exec "$ns1" $NFT list counter inet payload_inet input | grep -q "packets 0" && exit 1 ++ ip netns exec "$ns1" $NFT list counter inet payload_inet mangle_input | grep -q "packets 0" && exit 1 ++ ip netns exec "$ns1" $NFT list counter inet payload_inet mangle_input_match | grep -q "packets 0" && exit 1 ++ ip netns exec "$ns1" $NFT list counter inet payload_inet output | grep -q "packets 0" && exit 1 ++ ip netns exec "$ns1" $NFT list counter inet payload_inet mangle_output | grep -q "packets 0" && exit 1 ++ ip netns exec "$ns1" $NFT list counter inet payload_inet mangle_output_match | grep -q "packets 0" && exit 1 + + # + # ... next stage +@@ -135,6 +137,8 @@ table inet payload_inet { + ip -net "$ns1" addr add $ns1_addr/$cidr dev br0 + ip -net "$ns1" link set up dev br0 + ++ sleep 3 ++ + RULESET="table bridge payload_bridge { + counter input {} + counter output {} +@@ -160,20 +164,20 @@ RULESET="table bridge payload_bridge { + + ip netns exec "$ns1" $NFT -f - <<< "$RULESET" || exit 1 + +- ip netns exec "$ns1" socat -u STDIN TCP:$nsx2_addr:8880,connect-timeout=2 < /dev/null > /dev/null +- ip netns exec "$ns1" socat -u STDIN TCP:$nsx2_addr:8881,connect-timeout=2 < /dev/null > /dev/null ++ ip netns exec "$ns1" socat -u STDIN TCP:$nsx2_addr:8880,connect-timeout=4 < /dev/null > /dev/null ++ ip netns exec "$ns1" socat -u STDIN TCP:$nsx2_addr:8881,connect-timeout=4 < /dev/null > /dev/null + +- ip netns exec "$ns2" socat -u STDIN TCP:$nsx1_addr:7770,connect-timeout=2 < /dev/null > /dev/null +- ip netns exec "$ns2" socat -u STDIN TCP:$nsx1_addr:7771,connect-timeout=2 < /dev/null > /dev/null ++ ip netns exec "$ns2" socat -u STDIN TCP:$nsx1_addr:7770,connect-timeout=4 < /dev/null > /dev/null ++ ip netns exec "$ns2" socat -u STDIN TCP:$nsx1_addr:7771,connect-timeout=4 < /dev/null > /dev/null + + ip netns exec "$ns1" $NFT list ruleset + +- ip netns exec "$ns1" $NFT list counter bridge payload_bridge input | grep -v "packets 0" > /dev/null || exit 1 +- ip netns exec "$ns1" $NFT list counter bridge payload_bridge mangle_input | grep -v "packets 0" > /dev/null || exit 1 +- ip netns exec "$ns1" $NFT list counter bridge payload_bridge mangle_input_match | grep -v "packets 0" > /dev/null || exit 1 +- ip netns exec "$ns1" $NFT list counter bridge payload_bridge output | grep -v "packets 0" > /dev/null || exit 1 +- ip netns exec "$ns1" $NFT list counter bridge payload_bridge mangle_output | grep -v "packets 0" > /dev/null || exit 1 +- ip netns exec "$ns1" $NFT list counter bridge payload_bridge mangle_output_match | grep -v "packets 0" > /dev/null || exit 1 ++ ip netns exec "$ns1" $NFT list counter bridge payload_bridge input | grep -q "packets 0" && exit 1 ++ ip netns exec "$ns1" $NFT list counter bridge payload_bridge mangle_input | grep -q "packets 0" && exit 1 ++ ip netns exec "$ns1" $NFT list counter bridge payload_bridge mangle_input_match | grep -q "packets 0" && exit 1 ++ ip netns exec "$ns1" $NFT list counter bridge payload_bridge output | grep -q "packets 0" && exit 1 ++ ip netns exec "$ns1" $NFT list counter bridge payload_bridge mangle_output | grep -q "packets 0" && exit 1 ++ ip netns exec "$ns1" $NFT list counter bridge payload_bridge mangle_output_match | grep -q "packets 0" && exit 1 + } + + run_test "4" "10.141.10.2" "10.141.10.3" "24" diff --git a/0128-proto-use-NFT_PAYLOAD_L4CSUM_PSEUDOHDR-flag-to-mangl.patch b/0128-proto-use-NFT_PAYLOAD_L4CSUM_PSEUDOHDR-flag-to-mangl.patch new file mode 100644 index 0000000..ae69e26 --- /dev/null +++ b/0128-proto-use-NFT_PAYLOAD_L4CSUM_PSEUDOHDR-flag-to-mangl.patch @@ -0,0 +1,260 @@ +From 88dc18ce56a07ee7527a0daccd19c7e737f971c6 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:10:25 +0200 +Subject: [PATCH] proto: use NFT_PAYLOAD_L4CSUM_PSEUDOHDR flag to mangle UDP + checksum + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit f89abfb4068d31f7279cae298abf25e0c077d2d3 + +commit f89abfb4068d31f7279cae298abf25e0c077d2d3 +Author: Pablo Neira Ayuso +Date: Mon Sep 9 12:48:33 2024 +0200 + + proto: use NFT_PAYLOAD_L4CSUM_PSEUDOHDR flag to mangle UDP checksum + + There are two mechanisms to update the UDP checksum field: + + 1) _CSUM_TYPE and _CSUM_OFFSET which specify the type of checksum + (e.g. inet) and offset where it is located. + 2) use NFT_PAYLOAD_L4CSUM_PSEUDOHDR flag to use layer 4 kernel + protocol parser. + + The problem with 1) is that it is inconditional, that is, csum_type and + csum_offset cannot deal with zero UDP checksum. + + Use NFT_PAYLOAD_L4CSUM_PSEUDOHDR flag instead since it relies on the + layer 4 kernel parser which skips updating zero UDP checksum. + + Extend test coverage for the UDP mangling with and without zero + checksum. + + Fixes: e6c9174e13b2 ("proto: add checksum key information to struct proto_desc") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/netlink_linearize.c | 2 + + src/proto.c | 2 - + tests/shell/testcases/packetpath/payload | 129 +++++++++++++++++------ + 3 files changed, 99 insertions(+), 34 deletions(-) + +diff --git a/src/netlink_linearize.c b/src/netlink_linearize.c +index a2a1cd5..96308ff 100644 +--- a/src/netlink_linearize.c ++++ b/src/netlink_linearize.c +@@ -1112,6 +1112,8 @@ static void netlink_gen_payload_stmt(struct netlink_linearize_ctx *ctx, + } + if ((expr->payload.base == PROTO_BASE_NETWORK_HDR && desc && + payload_needs_l4csum_update_pseudohdr(expr, desc)) || ++ (expr->payload.base == PROTO_BASE_TRANSPORT_HDR && desc && ++ desc == &proto_udp) || + expr->payload.base == PROTO_BASE_INNER_HDR) + nftnl_expr_set_u32(nle, NFTNL_EXPR_PAYLOAD_FLAGS, + NFT_PAYLOAD_L4CSUM_PSEUDOHDR); +diff --git a/src/proto.c b/src/proto.c +index 553b6a4..05ddb07 100644 +--- a/src/proto.c ++++ b/src/proto.c +@@ -535,8 +535,6 @@ const struct proto_desc proto_udp = { + .name = "udp", + .id = PROTO_DESC_UDP, + .base = PROTO_BASE_TRANSPORT_HDR, +- .checksum_key = UDPHDR_CHECKSUM, +- .checksum_type = NFT_PAYLOAD_CSUM_INET, + .templates = { + [UDPHDR_SPORT] = INET_SERVICE("sport", struct udphdr, source), + [UDPHDR_DPORT] = INET_SERVICE("dport", struct udphdr, dest), +diff --git a/tests/shell/testcases/packetpath/payload b/tests/shell/testcases/packetpath/payload +index 1fb86aa..83e0b7f 100755 +--- a/tests/shell/testcases/packetpath/payload ++++ b/tests/shell/testcases/packetpath/payload +@@ -19,6 +19,28 @@ run_test() + ns1_addr=$2 + ns2_addr=$3 + cidr=$4 ++ mode=$5 ++ ++ case $mode in ++ "udp") ++ l4proto="udp" ++ udp_checksum="udp checksum != 0" ++ udp_zero_checksum="" ++ ;; ++ "udp-zero-checksum") ++ l4proto="udp" ++ udp_checksum="udp checksum 0" ++ udp_zero_checksum="udp checksum set 0" ++ ;; ++ "tcp") ++ l4proto="tcp" ++ udp_checksum="" ++ udp_zero_checksum="" ++ ;; ++ *) ++ echo "unexpected, incorrect mode" ++ exit 0 ++ esac + + # socat needs square brackets, ie. [abcd::2] + if [ $1 -eq 6 ]; then +@@ -54,16 +76,18 @@ RULESET="table netdev payload_netdev { + + chain ingress { + type filter hook ingress device veth0 priority 0; +- tcp dport 7777 counter name ingress +- tcp dport 7778 tcp dport set 7779 counter name mangle_ingress +- tcp dport 7779 counter name mangle_ingress_match ++ $udp_zero_checksum ++ $l4proto dport 7777 counter name ingress ++ $l4proto dport 7778 $l4proto dport set 7779 $udp_checksum counter name mangle_ingress ++ $l4proto dport 7779 counter name mangle_ingress_match + } + + chain egress { + type filter hook egress device veth0 priority 0; +- tcp dport 8887 counter name egress +- tcp dport 8888 tcp dport set 8889 counter name mangle_egress +- tcp dport 8889 counter name mangle_egress_match ++ $udp_zero_checksum ++ $l4proto dport 8887 counter name egress ++ $l4proto dport 8888 $l4proto dport set 8889 $udp_checksum counter name mangle_egress ++ $l4proto dport 8889 counter name mangle_egress_match + } + } + +@@ -77,32 +101,51 @@ table inet payload_inet { + + chain in { + type filter hook input priority 0; +- tcp dport 7770 counter name input +- tcp dport 7771 tcp dport set 7772 counter name mangle_input +- tcp dport 7772 counter name mangle_input_match ++ $udp_zero_checksum ++ $l4proto dport 7770 counter name input ++ $l4proto dport 7771 $l4proto dport set 7772 $udp_checksum counter name mangle_input ++ $l4proto dport 7772 counter name mangle_input_match + } + + chain out { + type filter hook output priority 0; +- tcp dport 8880 counter name output +- tcp dport 8881 tcp dport set 8882 counter name mangle_output +- tcp dport 8882 counter name mangle_output_match ++ $udp_zero_checksum ++ $l4proto dport 8880 counter name output ++ $l4proto dport 8881 $l4proto dport set 8882 $udp_checksum counter name mangle_output ++ $l4proto dport 8882 counter name mangle_output_match + } + }" + + ip netns exec "$ns1" $NFT -f - <<< "$RULESET" || exit 1 + +- ip netns exec "$ns1" socat -u STDIN TCP:$nsx2_addr:8887,connect-timeout=4 < /dev/null > /dev/null +- ip netns exec "$ns1" socat -u STDIN TCP:$nsx2_addr:8888,connect-timeout=4 < /dev/null > /dev/null ++ case $l4proto in ++ "tcp") ++ ip netns exec "$ns1" socat -u STDIN TCP:$nsx2_addr:8887,connect-timeout=4 < /dev/null > /dev/null ++ ip netns exec "$ns1" socat -u STDIN TCP:$nsx2_addr:8888,connect-timeout=4 < /dev/null > /dev/null + +- ip netns exec "$ns1" socat -u STDIN TCP:$nsx2_addr:8880,connect-timeout=4 < /dev/null > /dev/null +- ip netns exec "$ns1" socat -u STDIN TCP:$nsx2_addr:8881,connect-timeout=4 < /dev/null > /dev/null ++ ip netns exec "$ns1" socat -u STDIN TCP:$nsx2_addr:8880,connect-timeout=4 < /dev/null > /dev/null ++ ip netns exec "$ns1" socat -u STDIN TCP:$nsx2_addr:8881,connect-timeout=4 < /dev/null > /dev/null + +- ip netns exec "$ns2" socat -u STDIN TCP:$nsx1_addr:7777,connect-timeout=4 < /dev/null > /dev/null +- ip netns exec "$ns2" socat -u STDIN TCP:$nsx1_addr:7778,connect-timeout=4 < /dev/null > /dev/null ++ ip netns exec "$ns2" socat -u STDIN TCP:$nsx1_addr:7777,connect-timeout=4 < /dev/null > /dev/null ++ ip netns exec "$ns2" socat -u STDIN TCP:$nsx1_addr:7778,connect-timeout=4 < /dev/null > /dev/null + +- ip netns exec "$ns2" socat -u STDIN TCP:$nsx1_addr:7770,connect-timeout=4 < /dev/null > /dev/null +- ip netns exec "$ns2" socat -u STDIN TCP:$nsx1_addr:7771,connect-timeout=4 < /dev/null > /dev/null ++ ip netns exec "$ns2" socat -u STDIN TCP:$nsx1_addr:7770,connect-timeout=4 < /dev/null > /dev/null ++ ip netns exec "$ns2" socat -u STDIN TCP:$nsx1_addr:7771,connect-timeout=4 < /dev/null > /dev/null ++ ;; ++ "udp") ++ ip netns exec "$ns1" bash -c "echo 'AA' | socat -u STDIN UDP:$nsx2_addr:8887 > /dev/null" ++ ip netns exec "$ns1" bash -c "echo 'AA' | socat -u STDIN UDP:$nsx2_addr:8888 > /dev/null" ++ ++ ip netns exec "$ns1" bash -c "echo 'AA' | socat -u STDIN UDP:$nsx2_addr:8880 > /dev/null" ++ ip netns exec "$ns1" bash -c "echo 'AA' | socat -u STDIN UDP:$nsx2_addr:8881 > /dev/null" ++ ++ ip netns exec "$ns2" bash -c "echo 'AA' | socat -u STDIN UDP:$nsx1_addr:7777 > /dev/null" ++ ip netns exec "$ns2" bash -c "echo 'AA' | socat -u STDIN UDP:$nsx1_addr:7778 > /dev/null" ++ ++ ip netns exec "$ns2" bash -c "echo 'AA' | socat -u STDIN UDP:$nsx1_addr:7770 > /dev/null" ++ ip netns exec "$ns2" bash -c "echo 'AA' | socat -u STDIN UDP:$nsx1_addr:7771 > /dev/null" ++ ;; ++ esac + + ip netns exec "$ns1" $NFT list ruleset + +@@ -149,26 +192,39 @@ RULESET="table bridge payload_bridge { + + chain in { + type filter hook input priority 0; +- tcp dport 7770 counter name input +- tcp dport 7771 tcp dport set 7772 counter name mangle_input +- tcp dport 7772 counter name mangle_input_match ++ $udp_zero_checksum ++ $l4proto dport 7770 counter name input ++ $l4proto dport 7771 $l4proto dport set 7772 $udp_checksum counter name mangle_input ++ $l4proto dport 7772 counter name mangle_input_match + } + + chain out { + type filter hook output priority 0; +- tcp dport 8880 counter name output +- tcp dport 8881 tcp dport set 8882 counter name mangle_output +- tcp dport 8882 counter name mangle_output_match ++ $udp_zero_checksum ++ $l4proto dport 8880 counter name output ++ $l4proto dport 8881 $l4proto dport set 8882 $udp_checksum counter name mangle_output ++ $l4proto dport 8882 counter name mangle_output_match + } + }" + + ip netns exec "$ns1" $NFT -f - <<< "$RULESET" || exit 1 + +- ip netns exec "$ns1" socat -u STDIN TCP:$nsx2_addr:8880,connect-timeout=4 < /dev/null > /dev/null +- ip netns exec "$ns1" socat -u STDIN TCP:$nsx2_addr:8881,connect-timeout=4 < /dev/null > /dev/null ++ case $l4proto in ++ "tcp") ++ ip netns exec "$ns1" socat -u STDIN TCP:$nsx2_addr:8880,connect-timeout=4 < /dev/null > /dev/null ++ ip netns exec "$ns1" socat -u STDIN TCP:$nsx2_addr:8881,connect-timeout=4 < /dev/null > /dev/null ++ ++ ip netns exec "$ns2" socat -u STDIN TCP:$nsx1_addr:7770,connect-timeout=4 < /dev/null > /dev/null ++ ip netns exec "$ns2" socat -u STDIN TCP:$nsx1_addr:7771,connect-timeout=4 < /dev/null > /dev/null ++ ;; ++ "udp") ++ ip netns exec "$ns1" bash -c "echo 'AA' | socat -u STDIN UDP:$nsx2_addr:8880 > /dev/null" ++ ip netns exec "$ns1" bash -c "echo 'AA' | socat -u STDIN UDP:$nsx2_addr:8881 > /dev/null" + +- ip netns exec "$ns2" socat -u STDIN TCP:$nsx1_addr:7770,connect-timeout=4 < /dev/null > /dev/null +- ip netns exec "$ns2" socat -u STDIN TCP:$nsx1_addr:7771,connect-timeout=4 < /dev/null > /dev/null ++ ip netns exec "$ns2" bash -c "echo 'AA' | socat -u STDIN UDP:$nsx1_addr:7770 > /dev/null" ++ ip netns exec "$ns2" bash -c "echo 'AA' | socat -u STDIN UDP:$nsx1_addr:7771 > /dev/null" ++ ;; ++ esac + + ip netns exec "$ns1" $NFT list ruleset + +@@ -180,7 +236,16 @@ RULESET="table bridge payload_bridge { + ip netns exec "$ns1" $NFT list counter bridge payload_bridge mangle_output_match | grep -q "packets 0" && exit 1 + } + +-run_test "4" "10.141.10.2" "10.141.10.3" "24" ++run_test "4" "10.141.10.2" "10.141.10.3" "24" "tcp" ++cleanup ++run_test 6 "abcd::2" "abcd::3" "64" "tcp" ++cleanup ++run_test "4" "10.141.10.2" "10.141.10.3" "24" "udp" ++cleanup ++run_test 6 "abcd::2" "abcd::3" "64" "udp" ++cleanup ++run_test "4" "10.141.10.2" "10.141.10.3" "24" "udp-zero-checksum" + cleanup +-run_test 6 "abcd::2" "abcd::3" "64" ++run_test 6 "abcd::2" "abcd::3" "64" "udp-zero-checksum" + # trap calls cleanup ++exit 0 diff --git a/0129-tests-py-fix-up-udp-csum-fixup-output.patch b/0129-tests-py-fix-up-udp-csum-fixup-output.patch new file mode 100644 index 0000000..926efb4 --- /dev/null +++ b/0129-tests-py-fix-up-udp-csum-fixup-output.patch @@ -0,0 +1,48 @@ +From 15ef6cc22a6fe5ff638f140fc0df37c9c168d516 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:10:25 +0200 +Subject: [PATCH] tests: py: fix up udp csum fixup output + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 036a1eb3304995e4e000c552b9dfd33b1073ddb6 + +commit 036a1eb3304995e4e000c552b9dfd33b1073ddb6 +Author: Florian Westphal +Date: Wed Sep 11 14:23:01 2024 +0200 + + tests: py: fix up udp csum fixup output + + Preceeding commit switched udp to use the inkernel csum parser, so tests + warn: + + WARNING: line 7: 'add rule ip test-ip4 input iif "lo" udp checksum set 0': + '[ payload write reg 1 => 2b @ transport header + 6 csum_type 1 csum_off 6 csum_flags 0x0 ]' mismatches + '[ payload write reg 1 => 2b @ transport header + 6 csum_type 0 csum_off 0 csum_flags 0x1 ]' + + Fixes: f89abfb4068d ("proto: use NFT_PAYLOAD_L4CSUM_PSEUDOHDR flag to mangle UDP checksum") + Signed-off-by: Florian Westphal + +Signed-off-by: Phil Sutter +--- + tests/py/inet/udp.t.payload | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/tests/py/inet/udp.t.payload b/tests/py/inet/udp.t.payload +index e6beda7..32f7f8c 100644 +--- a/tests/py/inet/udp.t.payload ++++ b/tests/py/inet/udp.t.payload +@@ -236,7 +236,7 @@ inet test-inet input + [ meta load l4proto => reg 1 ] + [ cmp eq reg 1 0x00000011 ] + [ immediate reg 1 0x00000000 ] +- [ payload write reg 1 => 2b @ transport header + 6 csum_type 1 csum_off 6 csum_flags 0x0 ] ++ [ payload write reg 1 => 2b @ transport header + 6 csum_type 0 csum_off 0 csum_flags 0x1 ] + + # iif "lo" udp dport set 65535 + inet test-inet input +@@ -245,4 +245,4 @@ inet test-inet input + [ meta load l4proto => reg 1 ] + [ cmp eq reg 1 0x00000011 ] + [ immediate reg 1 0x0000ffff ] +- [ payload write reg 1 => 2b @ transport header + 2 csum_type 1 csum_off 6 csum_flags 0x0 ] ++ [ payload write reg 1 => 2b @ transport header + 2 csum_type 0 csum_off 0 csum_flags 0x1 ] diff --git a/0130-cache-initialize-filter-when-fetching-implicit-chain.patch b/0130-cache-initialize-filter-when-fetching-implicit-chain.patch new file mode 100644 index 0000000..399d833 --- /dev/null +++ b/0130-cache-initialize-filter-when-fetching-implicit-chain.patch @@ -0,0 +1,55 @@ +From 88488893e6f99f3feb97e3475b0d8cadd4ead948 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:10:25 +0200 +Subject: [PATCH] cache: initialize filter when fetching implicit chains + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit e3d2a5e852ceea587bfff5878e6e5c569f15116a + +commit e3d2a5e852ceea587bfff5878e6e5c569f15116a +Author: Pablo Neira Ayuso +Date: Tue Sep 17 19:18:09 2024 +0200 + + cache: initialize filter when fetching implicit chains + + ASAN reports: + + src/cache.c:734:25: runtime error: load of value 189, which is not a valid value for type '_Bool' + + because filter->reset.rule remains uninitialized. + + Initialize filter and replace existing construct to initialize table and + chain which leaves remaining fields uninitialized. + + Fixes: dbff26bfba83 ("cache: consolidate reset command") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/cache.c | 9 ++++----- + 1 file changed, 4 insertions(+), 5 deletions(-) + +diff --git a/src/cache.c b/src/cache.c +index 41c5a6f..0f38fb2 100644 +--- a/src/cache.c ++++ b/src/cache.c +@@ -1130,15 +1130,14 @@ err_ctx_list: + static int implicit_chain_cache(struct netlink_ctx *ctx, struct table *table, + const char *chain_name) + { +- struct nft_cache_filter filter; ++ struct nft_cache_filter filter = {}; + struct chain *chain; + int ret = 0; + + list_for_each_entry(chain, &table->chain_bindings, cache.list) { +- filter.list = (typeof(filter.list)) { +- .table = table->handle.table.name, +- .chain = chain->handle.chain.name, +- }; ++ filter.list.table = table->handle.table.name; ++ filter.list.chain = chain->handle.chain.name; ++ + ret = rule_init_cache(ctx, table, &filter); + } + diff --git a/0131-libnftables-json-fix-raw-payload-expression-document.patch b/0131-libnftables-json-fix-raw-payload-expression-document.patch new file mode 100644 index 0000000..722a58d --- /dev/null +++ b/0131-libnftables-json-fix-raw-payload-expression-document.patch @@ -0,0 +1,38 @@ +From 1e1a40f903ac7f8350d642d2a5bf55f78926a64b Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:10:26 +0200 +Subject: [PATCH] libnftables-json: fix raw payload expression documentation + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 570320ab9a0752c7749a6c9cc85b34a5e7ab91b5 + +commit 570320ab9a0752c7749a6c9cc85b34a5e7ab91b5 +Author: Eric Long +Date: Thu Oct 17 23:33:17 2024 +0800 + + libnftables-json: fix raw payload expression documentation + + Raw payload expression accesses payload data in bits, not bytes. + + Fixes: 872f373dc50f7 ("doc: Add JSON schema documentation") + Signed-off-by: Eric Long + Signed-off-by: Phil Sutter + +Signed-off-by: Phil Sutter +--- + doc/libnftables-json.adoc | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/doc/libnftables-json.adoc b/doc/libnftables-json.adoc +index cc70f0f..1bdb5c5 100644 +--- a/doc/libnftables-json.adoc ++++ b/doc/libnftables-json.adoc +@@ -1182,7 +1182,7 @@ ____ + + Construct a payload expression, i.e. a reference to a certain part of packet + data. The first form creates a raw payload expression to point at a random +-number (*len*) of bytes at a certain offset (*offset*) from a given reference ++number (*len*) of bits at a certain offset (*offset*) from a given reference + point (*base*). The following *base* values are accepted: + + *"ll"*:: diff --git a/0132-src-collapse-set-element-commands-from-parser.patch b/0132-src-collapse-set-element-commands-from-parser.patch new file mode 100644 index 0000000..cd48178 --- /dev/null +++ b/0132-src-collapse-set-element-commands-from-parser.patch @@ -0,0 +1,339 @@ +From da6c7cf1470c59c094d8f1bf87ef60abd0e307ca Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:10:26 +0200 +Subject: [PATCH] src: collapse set element commands from parser + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 20f1c60ac8c88be3bdf3096083b24ada06570a77 + +commit 20f1c60ac8c88be3bdf3096083b24ada06570a77 +Author: Pablo Neira Ayuso +Date: Wed Oct 23 11:43:58 2024 +0200 + + src: collapse set element commands from parser + + 498a5f0c219d ("rule: collapse set element commands") does not help to + reduce memory consumption in the case of large sets defined by one + element per line: + + add element ip x y { 1.1.1.1 } + add element ip x y { 1.1.1.2 } + ... + + This patch reduces memory consumption by ~75%, set elements are + collapsed into an existing cmd object wherever possible to reduce the + number of cmd objects. + + This patch also adds a special case for variables for sets similar to: + + be055af5c58d ("cmd: skip variable set elements when collapsing commands") + + This patch requires this small kernel fix: + + commit b53c116642502b0c85ecef78bff4f826a7dd4145 + Author: Pablo Neira Ayuso + Date: Fri May 20 00:02:06 2022 +0200 + + netfilter: nf_tables: set element extended ACK reporting support + + which is already included in recent -stable kernels: + + # cat ruleset.nft + add table ip x + add chain ip x y + add set ip x y { type ipv4_addr; } + create element ip x y { 1.1.1.1 } + create element ip x y { 1.1.1.1 } + + # nft -f ruleset.nft + ruleset.nft:5:25-31: Error: Could not process rule: File exists + create element ip x y { 1.1.1.1 } + ^^^^^^^ + + since there is no need to relate commands via sequence number anymore, + this allows also removes the uncollapse step. + + Fixes: 498a5f0c219d ("rule: collapse set element commands") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + include/cmd.h | 7 +-- + include/expression.h | 1 - + include/list.h | 11 +++++ + include/rule.h | 1 - + src/cmd.c | 105 +++++++++++-------------------------------- + src/libnftables.c | 7 --- + src/parser_bison.y | 13 ++++++ + src/rule.c | 1 - + 8 files changed, 54 insertions(+), 92 deletions(-) + +diff --git a/include/cmd.h b/include/cmd.h +index 92a4152..0a8779b 100644 +--- a/include/cmd.h ++++ b/include/cmd.h +@@ -2,12 +2,13 @@ + #define _NFT_CMD_H_ + + void cmd_add_loc(struct cmd *cmd, uint16_t offset, const struct location *loc); ++struct mnl_err; + void nft_cmd_error(struct netlink_ctx *ctx, struct cmd *cmd, + struct mnl_err *err); + ++bool nft_cmd_collapse_elems(enum cmd_ops op, struct list_head *cmds, ++ struct handle *handle, struct expr *init); ++ + void nft_cmd_expand(struct cmd *cmd); +-void nft_cmd_post_expand(struct cmd *cmd); +-bool nft_cmd_collapse(struct list_head *cmds); +-void nft_cmd_uncollapse(struct list_head *cmds); + + #endif +diff --git a/include/expression.h b/include/expression.h +index e143e71..bf6df76 100644 +--- a/include/expression.h ++++ b/include/expression.h +@@ -254,7 +254,6 @@ struct expr { + enum expr_types etype:8; + enum ops op:8; + unsigned int len; +- struct cmd *cmd; + + union { + struct { +diff --git a/include/list.h b/include/list.h +index 857921e..37fbe3e 100644 +--- a/include/list.h ++++ b/include/list.h +@@ -348,6 +348,17 @@ static inline void list_splice_tail_init(struct list_head *list, + #define list_first_entry(ptr, type, member) \ + list_entry((ptr)->next, type, member) + ++/** ++ * list_last_entry - get the last element from a list ++ * @ptr: the list head to take the element from. ++ * @type: the type of the struct this is embedded in. ++ * @member: the name of the list_head within the struct. ++ * ++ * Note, that list is expected to be not empty. ++ */ ++#define list_last_entry(ptr, type, member) \ ++ list_entry((ptr)->prev, type, member) ++ + /** + * list_next_entry - get the next element in list + * @pos: the type * to cursor +diff --git a/include/rule.h b/include/rule.h +index a8bb11f..36f8eaa 100644 +--- a/include/rule.h ++++ b/include/rule.h +@@ -711,7 +711,6 @@ struct cmd { + enum cmd_obj obj; + struct handle handle; + uint32_t seqnum; +- struct list_head collapse_list; + union { + void *data; + struct expr *expr; +diff --git a/src/cmd.c b/src/cmd.c +index 8f465de..46738d2 100644 +--- a/src/cmd.c ++++ b/src/cmd.c +@@ -355,6 +355,32 @@ static void nft_cmd_expand_chain(struct chain *chain, struct list_head *new_cmds + } + } + ++bool nft_cmd_collapse_elems(enum cmd_ops op, struct list_head *cmds, ++ struct handle *handle, struct expr *init) ++{ ++ struct cmd *last_cmd; ++ ++ if (list_empty(cmds)) ++ return false; ++ ++ if (init->etype == EXPR_VARIABLE) ++ return false; ++ ++ last_cmd = list_last_entry(cmds, struct cmd, list); ++ if (last_cmd->op != op || ++ last_cmd->obj != CMD_OBJ_ELEMENTS || ++ last_cmd->expr->etype == EXPR_VARIABLE || ++ last_cmd->handle.family != handle->family || ++ strcmp(last_cmd->handle.table.name, handle->table.name) || ++ strcmp(last_cmd->handle.set.name, handle->set.name)) ++ return false; ++ ++ list_splice_tail_init(&init->expressions, &last_cmd->expr->expressions); ++ last_cmd->expr->size += init->size; ++ ++ return true; ++} ++ + void nft_cmd_expand(struct cmd *cmd) + { + struct list_head new_cmds; +@@ -436,82 +462,3 @@ void nft_cmd_expand(struct cmd *cmd) + break; + } + } +- +-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 (cmd->expr->etype == EXPR_VARIABLE) +- continue; +- +- if (!elems) { +- elems = cmd; +- continue; +- } +- +- if (cmd->op != elems->op) { +- elems = cmd; +- continue; +- } +- +- if (elems->handle.family != cmd->handle.family || +- 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) { +- if (cmd->elem.set) +- collapse_cmd->elem.set = set_get(cmd->elem.set); +- +- list_add(&collapse_cmd->list, &cmd->list); +- } +- } +-} +diff --git a/src/libnftables.c b/src/libnftables.c +index b6bbece..2748b9c 100644 +--- a/src/libnftables.c ++++ b/src/libnftables.c +@@ -513,7 +513,6 @@ 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; + +@@ -529,9 +528,6 @@ 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(cmd, cmds, list) { + if (cmd->op != CMD_ADD && + cmd->op != CMD_CREATE) +@@ -553,9 +549,6 @@ static int nft_evaluate(struct nft_ctx *nft, struct list_head *msgs, + } + } + +- if (collapsed) +- nft_cmd_uncollapse(cmds); +- + if (err < 0 || nft->state->nerrs) + return -1; + +diff --git a/src/parser_bison.y b/src/parser_bison.y +index 25a285c..9bfd464 100644 +--- a/src/parser_bison.y ++++ b/src/parser_bison.y +@@ -35,6 +35,7 @@ + #include + + #include ++#include + #include + #include + #include +@@ -1215,6 +1216,12 @@ add_cmd : TABLE table_spec + } + | ELEMENT set_spec set_block_expr + { ++ if (nft_cmd_collapse_elems(CMD_ADD, state->cmds, &$2, $3)) { ++ handle_free(&$2); ++ expr_free($3); ++ $$ = NULL; ++ break; ++ } + $$ = cmd_alloc(CMD_ADD, CMD_OBJ_ELEMENTS, &$2, &@$, $3); + } + | FLOWTABLE flowtable_spec flowtable_block_alloc +@@ -1332,6 +1339,12 @@ create_cmd : TABLE table_spec + } + | ELEMENT set_spec set_block_expr + { ++ if (nft_cmd_collapse_elems(CMD_CREATE, state->cmds, &$2, $3)) { ++ handle_free(&$2); ++ expr_free($3); ++ $$ = NULL; ++ break; ++ } + $$ = cmd_alloc(CMD_CREATE, CMD_OBJ_ELEMENTS, &$2, &@$, $3); + } + | FLOWTABLE flowtable_spec flowtable_block_alloc +diff --git a/src/rule.c b/src/rule.c +index 683e69b..5d96a15 100644 +--- a/src/rule.c ++++ b/src/rule.c +@@ -1332,7 +1332,6 @@ struct cmd *cmd_alloc(enum cmd_ops op, enum cmd_obj obj, + cmd->attr = xzalloc_array(NFT_NLATTR_LOC_MAX, + sizeof(struct nlerr_loc)); + cmd->attr_array_len = NFT_NLATTR_LOC_MAX; +- init_list_head(&cmd->collapse_list); + + return cmd; + } diff --git a/0133-rule-netlink-attribute-offset-is-uint32_t-for-struct.patch b/0133-rule-netlink-attribute-offset-is-uint32_t-for-struct.patch new file mode 100644 index 0000000..77b375e --- /dev/null +++ b/0133-rule-netlink-attribute-offset-is-uint32_t-for-struct.patch @@ -0,0 +1,58 @@ +From 338b9d5a5643dddbdc2700592dd67ed2c86b9c8d Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:10:26 +0200 +Subject: [PATCH] rule: netlink attribute offset is uint32_t for struct + nlerr_loc + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 42b081df747729b0d83b69d2816be4091af56a58 + +commit 42b081df747729b0d83b69d2816be4091af56a58 +Author: Pablo Neira Ayuso +Date: Thu Oct 24 00:08:24 2024 +0200 + + rule: netlink attribute offset is uint32_t for struct nlerr_loc + + The maximum netlink message length (nlh->nlmsg_len) is uint32_t, struct + nlerr_loc stores the offset to the netlink attribute which must be + uint32_t, not uint16_t. + + While at it, remove check for zero netlink attribute offset in + nft_cmd_error() which should not ever happen, likely this check was + there to prevent the uint16_t offset overflow. + + Fixes: f8aec603aa7e ("src: initial extended netlink error reporting") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + include/rule.h | 2 +- + src/cmd.c | 2 -- + 2 files changed, 1 insertion(+), 3 deletions(-) + +diff --git a/include/rule.h b/include/rule.h +index 36f8eaa..2dfa371 100644 +--- a/include/rule.h ++++ b/include/rule.h +@@ -688,7 +688,7 @@ void monitor_free(struct monitor *m); + #define NFT_NLATTR_LOC_MAX 32 + + struct nlerr_loc { +- uint16_t offset; ++ uint32_t offset; + const struct location *location; + }; + +diff --git a/src/cmd.c b/src/cmd.c +index 46738d2..39be5f4 100644 +--- a/src/cmd.c ++++ b/src/cmd.c +@@ -300,8 +300,6 @@ void nft_cmd_error(struct netlink_ctx *ctx, struct cmd *cmd, + uint32_t i; + + for (i = 0; i < cmd->num_attrs; i++) { +- if (!cmd->attr[i].offset) +- break; + if (cmd->attr[i].offset == err->offset) + loc = cmd->attr[i].location; + } diff --git a/0134-mnl-update-cmd_add_loc-to-take-struct-nlmsghdr.patch b/0134-mnl-update-cmd_add_loc-to-take-struct-nlmsghdr.patch new file mode 100644 index 0000000..2473269 --- /dev/null +++ b/0134-mnl-update-cmd_add_loc-to-take-struct-nlmsghdr.patch @@ -0,0 +1,312 @@ +From 681a5006955bc7822d29ea2ade1017b362c35876 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:10:26 +0200 +Subject: [PATCH] mnl: update cmd_add_loc() to take struct nlmsghdr + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit f7c2b27c9f8356c634f0405347444e03e10e151b + +commit f7c2b27c9f8356c634f0405347444e03e10e151b +Author: Pablo Neira Ayuso +Date: Wed Oct 23 23:07:31 2024 +0200 + + mnl: update cmd_add_loc() to take struct nlmsghdr + + To prepare for a fix for very large sets. + + No functional change is intended. + + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + include/cmd.h | 2 +- + src/cmd.c | 4 +-- + src/mnl.c | 77 +++++++++++++++++++++++++-------------------------- + 3 files changed, 41 insertions(+), 42 deletions(-) + +diff --git a/include/cmd.h b/include/cmd.h +index 0a8779b..cf7e43b 100644 +--- a/include/cmd.h ++++ b/include/cmd.h +@@ -1,7 +1,7 @@ + #ifndef _NFT_CMD_H_ + #define _NFT_CMD_H_ + +-void cmd_add_loc(struct cmd *cmd, uint16_t offset, const struct location *loc); ++void cmd_add_loc(struct cmd *cmd, const struct nlmsghdr *nlh, const struct location *loc); + struct mnl_err; + void nft_cmd_error(struct netlink_ctx *ctx, struct cmd *cmd, + struct mnl_err *err); +diff --git a/src/cmd.c b/src/cmd.c +index 39be5f4..4cd3a07 100644 +--- a/src/cmd.c ++++ b/src/cmd.c +@@ -17,14 +17,14 @@ + #include + #include + +-void cmd_add_loc(struct cmd *cmd, uint16_t offset, const struct location *loc) ++void cmd_add_loc(struct cmd *cmd, const struct nlmsghdr *nlh, const struct location *loc) + { + if (cmd->num_attrs >= cmd->attr_array_len) { + cmd->attr_array_len *= 2; + cmd->attr = xrealloc(cmd->attr, sizeof(struct nlerr_loc) * cmd->attr_array_len); + } + +- cmd->attr[cmd->num_attrs].offset = offset; ++ cmd->attr[cmd->num_attrs].offset = nlh->nlmsg_len; + cmd->attr[cmd->num_attrs].location = loc; + cmd->num_attrs++; + } +diff --git a/src/mnl.c b/src/mnl.c +index 9bddf10..7a04e77 100644 +--- a/src/mnl.c ++++ b/src/mnl.c +@@ -472,7 +472,7 @@ static int mnl_nft_expr_build_cb(struct nftnl_expr *nle, void *data) + + eloc = nft_expr_loc_find(nle, ctx->lctx); + if (eloc) +- cmd_add_loc(cmd, nlh->nlmsg_len, eloc->loc); ++ cmd_add_loc(cmd, nlh, eloc->loc); + + nest = mnl_attr_nest_start(nlh, NFTA_LIST_ELEM); + nftnl_expr_build_payload(nlh, nle); +@@ -525,9 +525,9 @@ int mnl_nft_rule_add(struct netlink_ctx *ctx, struct cmd *cmd, + cmd->handle.family, + NLM_F_CREATE | flags, ctx->seqnum); + +- cmd_add_loc(cmd, nlh->nlmsg_len, &h->table.location); ++ cmd_add_loc(cmd, nlh, &h->table.location); + mnl_attr_put_strz(nlh, NFTA_RULE_TABLE, h->table.name); +- cmd_add_loc(cmd, nlh->nlmsg_len, &h->chain.location); ++ cmd_add_loc(cmd, nlh, &h->chain.location); + + if (h->chain_id) + mnl_attr_put_u32(nlh, NFTA_RULE_CHAIN_ID, htonl(h->chain_id)); +@@ -576,11 +576,11 @@ int mnl_nft_rule_replace(struct netlink_ctx *ctx, struct cmd *cmd) + cmd->handle.family, + NLM_F_REPLACE | flags, ctx->seqnum); + +- cmd_add_loc(cmd, nlh->nlmsg_len, &h->table.location); ++ cmd_add_loc(cmd, nlh, &h->table.location); + mnl_attr_put_strz(nlh, NFTA_RULE_TABLE, h->table.name); +- cmd_add_loc(cmd, nlh->nlmsg_len, &h->chain.location); ++ cmd_add_loc(cmd, nlh, &h->chain.location); + mnl_attr_put_strz(nlh, NFTA_RULE_CHAIN, h->chain.name); +- cmd_add_loc(cmd, nlh->nlmsg_len, &h->handle.location); ++ cmd_add_loc(cmd, nlh, &h->handle.location); + mnl_attr_put_u64(nlh, NFTA_RULE_HANDLE, htobe64(h->handle.id)); + + mnl_nft_rule_build_ctx_init(&rule_ctx, nlh, cmd, &lctx); +@@ -619,14 +619,14 @@ int mnl_nft_rule_del(struct netlink_ctx *ctx, struct cmd *cmd) + nftnl_rule_get_u32(nlr, NFTNL_RULE_FAMILY), + 0, ctx->seqnum); + +- cmd_add_loc(cmd, nlh->nlmsg_len, &h->table.location); ++ cmd_add_loc(cmd, nlh, &h->table.location); + mnl_attr_put_strz(nlh, NFTA_RULE_TABLE, h->table.name); + if (h->chain.name) { +- cmd_add_loc(cmd, nlh->nlmsg_len, &h->chain.location); ++ cmd_add_loc(cmd, nlh, &h->chain.location); + mnl_attr_put_strz(nlh, NFTA_RULE_CHAIN, h->chain.name); + } + if (h->handle.id) { +- cmd_add_loc(cmd, nlh->nlmsg_len, &h->handle.location); ++ cmd_add_loc(cmd, nlh, &h->handle.location); + mnl_attr_put_u64(nlh, NFTA_RULE_HANDLE, htobe64(h->handle.id)); + } + +@@ -790,12 +790,12 @@ static void mnl_nft_chain_devs_build(struct nlmsghdr *nlh, struct cmd *cmd) + + dev_array = nft_dev_array(dev_expr, &num_devs); + if (num_devs == 1) { +- cmd_add_loc(cmd, nlh->nlmsg_len, dev_array[0].location); ++ cmd_add_loc(cmd, nlh, dev_array[0].location); + mnl_attr_put_strz(nlh, NFTA_HOOK_DEV, dev_array[0].ifname); + } else { + nest_dev = mnl_attr_nest_start(nlh, NFTA_HOOK_DEVS); + for (i = 0; i < num_devs; i++) { +- cmd_add_loc(cmd, nlh->nlmsg_len, dev_array[i].location); ++ cmd_add_loc(cmd, nlh, dev_array[i].location); + mnl_attr_put_strz(nlh, NFTA_DEVICE_NAME, dev_array[i].ifname); + mnl_attr_nest_end(nlh, nest_dev); + } +@@ -840,9 +840,9 @@ int mnl_nft_chain_add(struct netlink_ctx *ctx, struct cmd *cmd, + cmd->handle.family, + NLM_F_CREATE | flags, ctx->seqnum); + +- cmd_add_loc(cmd, nlh->nlmsg_len, &cmd->handle.table.location); ++ cmd_add_loc(cmd, nlh, &cmd->handle.table.location); + mnl_attr_put_strz(nlh, NFTA_CHAIN_TABLE, cmd->handle.table.name); +- cmd_add_loc(cmd, nlh->nlmsg_len, &cmd->handle.chain.location); ++ cmd_add_loc(cmd, nlh, &cmd->handle.chain.location); + + if (!cmd->chain || !(cmd->chain->flags & CHAIN_F_BINDING)) { + mnl_attr_put_strz(nlh, NFTA_CHAIN_NAME, cmd->handle.chain.name); +@@ -859,7 +859,7 @@ int mnl_nft_chain_add(struct netlink_ctx *ctx, struct cmd *cmd, + if (cmd->chain && cmd->chain->policy) { + mpz_export_data(&policy, cmd->chain->policy->value, + BYTEORDER_HOST_ENDIAN, sizeof(int)); +- cmd_add_loc(cmd, nlh->nlmsg_len, &cmd->chain->policy->location); ++ cmd_add_loc(cmd, nlh, &cmd->chain->policy->location); + mnl_attr_put_u32(nlh, NFTA_CHAIN_POLICY, htonl(policy)); + } + +@@ -871,7 +871,7 @@ int mnl_nft_chain_add(struct netlink_ctx *ctx, struct cmd *cmd, + struct nlattr *nest; + + if (cmd->chain->type.str) { +- cmd_add_loc(cmd, nlh->nlmsg_len, &cmd->chain->type.loc); ++ cmd_add_loc(cmd, nlh, &cmd->chain->type.loc); + mnl_attr_put_strz(nlh, NFTA_CHAIN_TYPE, cmd->chain->type.str); + } + +@@ -947,13 +947,13 @@ int mnl_nft_chain_del(struct netlink_ctx *ctx, struct cmd *cmd) + cmd->handle.family, + 0, ctx->seqnum); + +- cmd_add_loc(cmd, nlh->nlmsg_len, &cmd->handle.table.location); ++ cmd_add_loc(cmd, nlh, &cmd->handle.table.location); + mnl_attr_put_strz(nlh, NFTA_CHAIN_TABLE, cmd->handle.table.name); + if (cmd->handle.chain.name) { +- cmd_add_loc(cmd, nlh->nlmsg_len, &cmd->handle.chain.location); ++ cmd_add_loc(cmd, nlh, &cmd->handle.chain.location); + mnl_attr_put_strz(nlh, NFTA_CHAIN_NAME, cmd->handle.chain.name); + } else if (cmd->handle.handle.id) { +- cmd_add_loc(cmd, nlh->nlmsg_len, &cmd->handle.handle.location); ++ cmd_add_loc(cmd, nlh, &cmd->handle.handle.location); + mnl_attr_put_u64(nlh, NFTA_CHAIN_HANDLE, + htobe64(cmd->handle.handle.id)); + } +@@ -1075,7 +1075,7 @@ int mnl_nft_table_add(struct netlink_ctx *ctx, struct cmd *cmd, + cmd->handle.family, + flags, ctx->seqnum); + +- cmd_add_loc(cmd, nlh->nlmsg_len, &cmd->handle.table.location); ++ cmd_add_loc(cmd, nlh, &cmd->handle.table.location); + mnl_attr_put_strz(nlh, NFTA_TABLE_NAME, cmd->handle.table.name); + nftnl_table_nlmsg_build_payload(nlh, nlt); + nftnl_table_free(nlt); +@@ -1104,10 +1104,10 @@ int mnl_nft_table_del(struct netlink_ctx *ctx, struct cmd *cmd) + cmd->handle.family, 0, ctx->seqnum); + + if (cmd->handle.table.name) { +- cmd_add_loc(cmd, nlh->nlmsg_len, &cmd->handle.table.location); ++ cmd_add_loc(cmd, nlh, &cmd->handle.table.location); + mnl_attr_put_strz(nlh, NFTA_TABLE_NAME, cmd->handle.table.name); + } else if (cmd->handle.handle.id) { +- cmd_add_loc(cmd, nlh->nlmsg_len, &cmd->handle.handle.location); ++ cmd_add_loc(cmd, nlh, &cmd->handle.handle.location); + mnl_attr_put_u64(nlh, NFTA_TABLE_HANDLE, + htobe64(cmd->handle.handle.id)); + } +@@ -1323,9 +1323,9 @@ int mnl_nft_set_add(struct netlink_ctx *ctx, struct cmd *cmd, + h->family, + NLM_F_CREATE | flags, ctx->seqnum); + +- cmd_add_loc(cmd, nlh->nlmsg_len, &h->table.location); ++ cmd_add_loc(cmd, nlh, &h->table.location); + mnl_attr_put_strz(nlh, NFTA_SET_TABLE, h->table.name); +- cmd_add_loc(cmd, nlh->nlmsg_len, &h->set.location); ++ cmd_add_loc(cmd, nlh, &h->set.location); + mnl_attr_put_strz(nlh, NFTA_SET_NAME, h->set.name); + + nftnl_set_nlmsg_build_payload(nlh, nls); +@@ -1357,13 +1357,13 @@ int mnl_nft_set_del(struct netlink_ctx *ctx, struct cmd *cmd) + h->family, + 0, ctx->seqnum); + +- cmd_add_loc(cmd, nlh->nlmsg_len, &cmd->handle.table.location); ++ cmd_add_loc(cmd, nlh, &cmd->handle.table.location); + mnl_attr_put_strz(nlh, NFTA_SET_TABLE, cmd->handle.table.name); + if (h->set.name) { +- cmd_add_loc(cmd, nlh->nlmsg_len, &cmd->handle.set.location); ++ cmd_add_loc(cmd, nlh, &cmd->handle.set.location); + mnl_attr_put_strz(nlh, NFTA_SET_NAME, cmd->handle.set.name); + } else if (h->handle.id) { +- cmd_add_loc(cmd, nlh->nlmsg_len, &cmd->handle.handle.location); ++ cmd_add_loc(cmd, nlh, &cmd->handle.handle.location); + mnl_attr_put_u64(nlh, NFTA_SET_HANDLE, + htobe64(cmd->handle.handle.id)); + } +@@ -1542,9 +1542,9 @@ int mnl_nft_obj_add(struct netlink_ctx *ctx, struct cmd *cmd, + NFT_MSG_NEWOBJ, cmd->handle.family, + NLM_F_CREATE | flags, ctx->seqnum); + +- cmd_add_loc(cmd, nlh->nlmsg_len, &cmd->handle.table.location); ++ cmd_add_loc(cmd, nlh, &cmd->handle.table.location); + mnl_attr_put_strz(nlh, NFTA_OBJ_TABLE, cmd->handle.table.name); +- cmd_add_loc(cmd, nlh->nlmsg_len, &cmd->handle.obj.location); ++ cmd_add_loc(cmd, nlh, &cmd->handle.obj.location); + mnl_attr_put_strz(nlh, NFTA_OBJ_NAME, cmd->handle.obj.name); + + nftnl_obj_nlmsg_build_payload(nlh, nlo); +@@ -1575,14 +1575,14 @@ int mnl_nft_obj_del(struct netlink_ctx *ctx, struct cmd *cmd, int type) + msg_type, cmd->handle.family, + 0, ctx->seqnum); + +- cmd_add_loc(cmd, nlh->nlmsg_len, &cmd->handle.table.location); ++ cmd_add_loc(cmd, nlh, &cmd->handle.table.location); + mnl_attr_put_strz(nlh, NFTA_OBJ_TABLE, cmd->handle.table.name); + + if (cmd->handle.obj.name) { +- cmd_add_loc(cmd, nlh->nlmsg_len, &cmd->handle.obj.location); ++ cmd_add_loc(cmd, nlh, &cmd->handle.obj.location); + mnl_attr_put_strz(nlh, NFTA_OBJ_NAME, cmd->handle.obj.name); + } else if (cmd->handle.handle.id) { +- cmd_add_loc(cmd, nlh->nlmsg_len, &cmd->handle.handle.location); ++ cmd_add_loc(cmd, nlh, &cmd->handle.handle.location); + mnl_attr_put_u64(nlh, NFTA_OBJ_HANDLE, + htobe64(cmd->handle.handle.id)); + } +@@ -1762,7 +1762,7 @@ next: + list_for_each_entry_from(expr, &set->expressions, list) { + nlse = alloc_nftnl_setelem(set, expr); + +- cmd_add_loc(cmd, nlh->nlmsg_len, &expr->location); ++ cmd_add_loc(cmd, nlh, &expr->location); + nest2 = mnl_attr_nest_start(nlh, ++i); + nftnl_set_elem_nlmsg_build_payload(nlh, nlse); + mnl_attr_nest_end(nlh, nest2); +@@ -2003,7 +2003,7 @@ static void mnl_nft_ft_devs_build(struct nlmsghdr *nlh, struct cmd *cmd) + dev_array = nft_dev_array(dev_expr, &num_devs); + nest_dev = mnl_attr_nest_start(nlh, NFTA_FLOWTABLE_HOOK_DEVS); + for (i = 0; i < num_devs; i++) { +- cmd_add_loc(cmd, nlh->nlmsg_len, dev_array[i].location); ++ cmd_add_loc(cmd, nlh, dev_array[i].location); + mnl_attr_put_strz(nlh, NFTA_DEVICE_NAME, dev_array[i].ifname); + } + +@@ -2035,9 +2035,9 @@ int mnl_nft_flowtable_add(struct netlink_ctx *ctx, struct cmd *cmd, + NFT_MSG_NEWFLOWTABLE, cmd->handle.family, + NLM_F_CREATE | flags, ctx->seqnum); + +- cmd_add_loc(cmd, nlh->nlmsg_len, &cmd->handle.table.location); ++ cmd_add_loc(cmd, nlh, &cmd->handle.table.location); + mnl_attr_put_strz(nlh, NFTA_FLOWTABLE_TABLE, cmd->handle.table.name); +- cmd_add_loc(cmd, nlh->nlmsg_len, &cmd->handle.flowtable.location); ++ cmd_add_loc(cmd, nlh, &cmd->handle.flowtable.location); + mnl_attr_put_strz(nlh, NFTA_FLOWTABLE_NAME, cmd->handle.flowtable.name); + + nftnl_flowtable_nlmsg_build_payload(nlh, flo); +@@ -2084,16 +2084,15 @@ int mnl_nft_flowtable_del(struct netlink_ctx *ctx, struct cmd *cmd) + msg_type, cmd->handle.family, + 0, ctx->seqnum); + +- cmd_add_loc(cmd, nlh->nlmsg_len, &cmd->handle.table.location); ++ cmd_add_loc(cmd, nlh, &cmd->handle.table.location); + mnl_attr_put_strz(nlh, NFTA_FLOWTABLE_TABLE, cmd->handle.table.name); + + if (cmd->handle.flowtable.name) { +- cmd_add_loc(cmd, nlh->nlmsg_len, +- &cmd->handle.flowtable.location); ++ cmd_add_loc(cmd, nlh, &cmd->handle.flowtable.location); + mnl_attr_put_strz(nlh, NFTA_FLOWTABLE_NAME, + cmd->handle.flowtable.name); + } else if (cmd->handle.handle.id) { +- cmd_add_loc(cmd, nlh->nlmsg_len, &cmd->handle.handle.location); ++ cmd_add_loc(cmd, nlh, &cmd->handle.handle.location); + mnl_attr_put_u64(nlh, NFTA_FLOWTABLE_HANDLE, + htobe64(cmd->handle.handle.id)); + } diff --git a/0135-mnl-rename-to-mnl_seqnum_alloc-to-mnl_seqnum_inc.patch b/0135-mnl-rename-to-mnl_seqnum_alloc-to-mnl_seqnum_inc.patch new file mode 100644 index 0000000..6d8feee --- /dev/null +++ b/0135-mnl-rename-to-mnl_seqnum_alloc-to-mnl_seqnum_inc.patch @@ -0,0 +1,78 @@ +From 987ec8909fca7fe608fb896805ad2cddea6a9e38 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:10:26 +0200 +Subject: [PATCH] mnl: rename to mnl_seqnum_alloc() to mnl_seqnum_inc() + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit b4ce90d52d564efaced298f6e9c575d6942ecf91 + +commit b4ce90d52d564efaced298f6e9c575d6942ecf91 +Author: Pablo Neira Ayuso +Date: Wed Oct 23 22:15:24 2024 +0200 + + mnl: rename to mnl_seqnum_alloc() to mnl_seqnum_inc() + + rename mnl_seqnum_alloc() to mnl_seqnum_inc(). + + No functional change is intended. + + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + include/mnl.h | 2 +- + src/libnftables.c | 6 +++--- + src/mnl.c | 2 +- + 3 files changed, 5 insertions(+), 5 deletions(-) + +diff --git a/include/mnl.h b/include/mnl.h +index cd5a205..ab38c21 100644 +--- a/include/mnl.h ++++ b/include/mnl.h +@@ -8,7 +8,7 @@ + + struct mnl_socket *nft_mnl_socket_open(void); + +-uint32_t mnl_seqnum_alloc(uint32_t *seqnum); ++uint32_t mnl_seqnum_inc(uint32_t *seqnum); + uint32_t mnl_genid_get(struct netlink_ctx *ctx); + + struct mnl_err { +diff --git a/src/libnftables.c b/src/libnftables.c +index 2748b9c..c363a3e 100644 +--- a/src/libnftables.c ++++ b/src/libnftables.c +@@ -36,9 +36,9 @@ static int nft_netlink(struct nft_ctx *nft, + if (list_empty(cmds)) + goto out; + +- batch_seqnum = mnl_batch_begin(ctx.batch, mnl_seqnum_alloc(&seqnum)); ++ batch_seqnum = mnl_batch_begin(ctx.batch, mnl_seqnum_inc(&seqnum)); + list_for_each_entry(cmd, cmds, list) { +- ctx.seqnum = cmd->seqnum = mnl_seqnum_alloc(&seqnum); ++ ctx.seqnum = cmd->seqnum = mnl_seqnum_inc(&seqnum); + ret = do_command(&ctx, cmd); + if (ret < 0) { + netlink_io_error(&ctx, &cmd->location, +@@ -49,7 +49,7 @@ static int nft_netlink(struct nft_ctx *nft, + num_cmds++; + } + if (!nft->check) +- mnl_batch_end(ctx.batch, mnl_seqnum_alloc(&seqnum)); ++ mnl_batch_end(ctx.batch, mnl_seqnum_inc(&seqnum)); + + if (!mnl_batch_ready(ctx.batch)) + goto out; +diff --git a/src/mnl.c b/src/mnl.c +index 7a04e77..d8d62e6 100644 +--- a/src/mnl.c ++++ b/src/mnl.c +@@ -68,7 +68,7 @@ struct mnl_socket *nft_mnl_socket_open(void) + return nf_sock; + } + +-uint32_t mnl_seqnum_alloc(unsigned int *seqnum) ++uint32_t mnl_seqnum_inc(unsigned int *seqnum) + { + return (*seqnum)++; + } diff --git a/0136-src-fix-extended-netlink-error-reporting-with-large-.patch b/0136-src-fix-extended-netlink-error-reporting-with-large-.patch new file mode 100644 index 0000000..768592f --- /dev/null +++ b/0136-src-fix-extended-netlink-error-reporting-with-large-.patch @@ -0,0 +1,193 @@ +From 47b48a608741a6ca71b2bf37b69afb849b0afeaf Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:10:26 +0200 +Subject: [PATCH] src: fix extended netlink error reporting with large set + elements + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 68d2de3ca6c6eb18f5b32f7b4324a85c9c6c358e + +commit 68d2de3ca6c6eb18f5b32f7b4324a85c9c6c358e +Author: Pablo Neira Ayuso +Date: Thu Oct 24 00:24:55 2024 +0200 + + src: fix extended netlink error reporting with large set elements + + Large sets can expand into several netlink messages, use sequence number + and attribute offset to correlate the set element and the location. + + When set element command expands into several netlink messages, + increment sequence number for each netlink message. Update struct cmd to + store the range of netlink messages that result from this command. + + struct nlerr_loc remains in the same size in x86_64. + + # nft -f set-65535.nft + set-65535.nft:65029:22-32: Error: Could not process rule: File exists + create element x y { 1.1.254.253 } + ^^^^^^^^^^^ + + Fixes: f8aec603aa7e ("src: initial extended netlink error reporting") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + include/rule.h | 4 +++- + src/cmd.c | 4 +++- + src/libnftables.c | 12 ++++++++---- + src/mnl.c | 9 +++++---- + src/parser_json.c | 4 ++-- + 5 files changed, 21 insertions(+), 12 deletions(-) + +diff --git a/include/rule.h b/include/rule.h +index 2dfa371..a82c794 100644 +--- a/include/rule.h ++++ b/include/rule.h +@@ -688,6 +688,7 @@ void monitor_free(struct monitor *m); + #define NFT_NLATTR_LOC_MAX 32 + + struct nlerr_loc { ++ uint32_t seqnum; + uint32_t offset; + const struct location *location; + }; +@@ -710,7 +711,8 @@ struct cmd { + enum cmd_ops op; + enum cmd_obj obj; + struct handle handle; +- uint32_t seqnum; ++ uint32_t seqnum_from; ++ uint32_t seqnum_to; + union { + void *data; + struct expr *expr; +diff --git a/src/cmd.c b/src/cmd.c +index 4cd3a07..6f6da35 100644 +--- a/src/cmd.c ++++ b/src/cmd.c +@@ -24,6 +24,7 @@ void cmd_add_loc(struct cmd *cmd, const struct nlmsghdr *nlh, const struct locat + cmd->attr = xrealloc(cmd->attr, sizeof(struct nlerr_loc) * cmd->attr_array_len); + } + ++ cmd->attr[cmd->num_attrs].seqnum = nlh->nlmsg_seq; + cmd->attr[cmd->num_attrs].offset = nlh->nlmsg_len; + cmd->attr[cmd->num_attrs].location = loc; + cmd->num_attrs++; +@@ -300,7 +301,8 @@ void nft_cmd_error(struct netlink_ctx *ctx, struct cmd *cmd, + uint32_t i; + + for (i = 0; i < cmd->num_attrs; i++) { +- if (cmd->attr[i].offset == err->offset) ++ if (cmd->attr[i].seqnum == err->seqnum && ++ cmd->attr[i].offset == err->offset) + loc = cmd->attr[i].location; + } + +diff --git a/src/libnftables.c b/src/libnftables.c +index c363a3e..653060b 100644 +--- a/src/libnftables.c ++++ b/src/libnftables.c +@@ -38,7 +38,7 @@ static int nft_netlink(struct nft_ctx *nft, + + batch_seqnum = mnl_batch_begin(ctx.batch, mnl_seqnum_inc(&seqnum)); + list_for_each_entry(cmd, cmds, list) { +- ctx.seqnum = cmd->seqnum = mnl_seqnum_inc(&seqnum); ++ ctx.seqnum = cmd->seqnum_from = mnl_seqnum_inc(&seqnum); + ret = do_command(&ctx, cmd); + if (ret < 0) { + netlink_io_error(&ctx, &cmd->location, +@@ -46,6 +46,8 @@ static int nft_netlink(struct nft_ctx *nft, + strerror(errno)); + goto out; + } ++ seqnum = cmd->seqnum_to = ctx.seqnum; ++ mnl_seqnum_inc(&seqnum); + num_cmds++; + } + if (!nft->check) +@@ -79,12 +81,14 @@ static int nft_netlink(struct nft_ctx *nft, + cmd = list_first_entry(cmds, struct cmd, list); + + list_for_each_entry_from(cmd, cmds, list) { +- last_seqnum = cmd->seqnum; +- if (err->seqnum == cmd->seqnum || ++ last_seqnum = cmd->seqnum_to; ++ if ((err->seqnum >= cmd->seqnum_from && ++ err->seqnum <= cmd->seqnum_to) || + err->seqnum == batch_seqnum) { + nft_cmd_error(&ctx, cmd, err); + errno = err->err; +- if (err->seqnum == cmd->seqnum) { ++ if (err->seqnum >= cmd->seqnum_from || ++ err->seqnum <= cmd->seqnum_to) { + mnl_err_list_free(err); + break; + } +diff --git a/src/mnl.c b/src/mnl.c +index d8d62e6..0f54d00 100644 +--- a/src/mnl.c ++++ b/src/mnl.c +@@ -1720,7 +1720,7 @@ static void netlink_dump_setelem_done(struct netlink_ctx *ctx) + static int mnl_nft_setelem_batch(const struct nftnl_set *nls, struct cmd *cmd, + struct nftnl_batch *batch, + enum nf_tables_msg_types msg_type, +- unsigned int flags, uint32_t seqnum, ++ unsigned int flags, uint32_t *seqnum, + const struct expr *set, + struct netlink_ctx *ctx) + { +@@ -1739,7 +1739,7 @@ static int mnl_nft_setelem_batch(const struct nftnl_set *nls, struct cmd *cmd, + next: + nlh = nftnl_nlmsg_build_hdr(nftnl_batch_buffer(batch), msg_type, + nftnl_set_get_u32(nls, NFTNL_SET_FAMILY), +- flags, seqnum); ++ flags, *seqnum); + + if (nftnl_set_is_set(nls, NFTNL_SET_TABLE)) { + mnl_attr_put_strz(nlh, NFTA_SET_ELEM_LIST_TABLE, +@@ -1772,6 +1772,7 @@ next: + if (mnl_nft_attr_nest_overflow(nlh, nest1, nest2)) { + mnl_attr_nest_end(nlh, nest1); + mnl_nft_batch_continue(batch); ++ mnl_seqnum_inc(seqnum); + goto next; + } + } +@@ -1806,7 +1807,7 @@ int mnl_nft_setelem_add(struct netlink_ctx *ctx, struct cmd *cmd, + netlink_dump_set(nls, ctx); + + err = mnl_nft_setelem_batch(nls, cmd, ctx->batch, NFT_MSG_NEWSETELEM, +- flags, ctx->seqnum, expr, ctx); ++ flags, &ctx->seqnum, expr, ctx); + nftnl_set_free(nls); + + return err; +@@ -1866,7 +1867,7 @@ int mnl_nft_setelem_del(struct netlink_ctx *ctx, struct cmd *cmd, + msg_type = NFT_MSG_DESTROYSETELEM; + + err = mnl_nft_setelem_batch(nls, cmd, ctx->batch, msg_type, 0, +- ctx->seqnum, init, ctx); ++ &ctx->seqnum, init, ctx); + nftnl_set_free(nls); + + return err; +diff --git a/src/parser_json.c b/src/parser_json.c +index 381d0f8..f7ab79f 100644 +--- a/src/parser_json.c ++++ b/src/parser_json.c +@@ -4244,13 +4244,13 @@ static json_t *seqnum_to_json(const uint32_t seqnum) + cur = json_cmd_assoc_list; + json_cmd_assoc_list = cur->next; + +- key = cur->cmd->seqnum % CMD_ASSOC_HSIZE; ++ key = cur->cmd->seqnum_from % CMD_ASSOC_HSIZE; + hlist_add_head(&cur->hnode, &json_cmd_assoc_hash[key]); + } + + key = seqnum % CMD_ASSOC_HSIZE; + hlist_for_each_entry(cur, n, &json_cmd_assoc_hash[key], hnode) { +- if (cur->cmd->seqnum == seqnum) ++ if (cur->cmd->seqnum_from == seqnum) + return cur->json; + } + diff --git a/0137-tests-monitor-fix-up-test-case-breakage.patch b/0137-tests-monitor-fix-up-test-case-breakage.patch new file mode 100644 index 0000000..0dcdc63 --- /dev/null +++ b/0137-tests-monitor-fix-up-test-case-breakage.patch @@ -0,0 +1,62 @@ +From 276b6a8b95059d2fa3c5b152ec13e4a268fc9c2d Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:10:26 +0200 +Subject: [PATCH] tests: monitor: fix up test case breakage + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit c416416b03d804663c5f7a738a3e1449eeb28157 + +commit c416416b03d804663c5f7a738a3e1449eeb28157 +Author: Florian Westphal +Date: Tue Oct 29 21:12:19 2024 +0100 + + tests: monitor: fix up test case breakage + + Monitor test fails: + + echo: running tests from file set-simple.t + echo output differs! + -add element ip t portrange { 1024-65535 } + add element ip t portrange { 100-200 } + +add element ip t portrange { 1024-65535 } + +# new generation 510 by process 129009 (nft) + + I also noticed -j mode did not work correctly, add missing json annotations + in set-concat-interval.t while at it. + + Signed-off-by: Florian Westphal + +Signed-off-by: Phil Sutter +--- + tests/monitor/testcases/set-concat-interval.t | 3 +++ + tests/monitor/testcases/set-simple.t | 5 +++-- + 2 files changed, 6 insertions(+), 2 deletions(-) + +diff --git a/tests/monitor/testcases/set-concat-interval.t b/tests/monitor/testcases/set-concat-interval.t +index 763dc31..75f3828 100644 +--- a/tests/monitor/testcases/set-concat-interval.t ++++ b/tests/monitor/testcases/set-concat-interval.t +@@ -10,3 +10,6 @@ I add map ip t s { typeof udp length . @ih,32,32 : verdict; flags interval; elem + O add map ip t s { typeof udp length . @ih,32,32 : verdict; flags interval; } + O add element ip t s { 20-80 . 0x14 : accept } + O add element ip t s { 1-10 . 0xa : drop } ++J {"add": {"map": {"family": "ip", "name": "s", "table": "t", "type": ["integer", "integer"], "handle": 0, "map": "verdict", "flags": ["interval"]}}} ++J {"add": {"element": {"family": "ip", "table": "t", "name": "s", "elem": {"set": [[{"concat": [{"range": [20, 80]}, 20]}, {"accept": null}]]}}}} ++J {"add": {"element": {"family": "ip", "table": "t", "name": "s", "elem": {"set": [[{"concat": [{"range": [1, 10]}, 10]}, {"drop": null}]]}}}} +diff --git a/tests/monitor/testcases/set-simple.t b/tests/monitor/testcases/set-simple.t +index 8ca4f32..6853a0e 100644 +--- a/tests/monitor/testcases/set-simple.t ++++ b/tests/monitor/testcases/set-simple.t +@@ -37,9 +37,10 @@ J {"add": {"element": {"family": "ip", "table": "t", "name": "portrange", "elem" + # make sure half open before other element works + I add element ip t portrange { 1024-65535 } + I add element ip t portrange { 100-200 } +-O - +-J {"add": {"element": {"family": "ip", "table": "t", "name": "portrange", "elem": {"set": [{"range": [1024, 65535]}]}}}} ++O add element ip t portrange { 100-200 } ++O add element ip t portrange { 1024-65535 } + J {"add": {"element": {"family": "ip", "table": "t", "name": "portrange", "elem": {"set": [{"range": [100, 200]}]}}}} ++J {"add": {"element": {"family": "ip", "table": "t", "name": "portrange", "elem": {"set": [{"range": [1024, 65535]}]}}}} + + # make sure deletion of elements works + I delete element ip t portrange { 0-10 } diff --git a/0138-doc-extend-description-of-fib-expression.patch b/0138-doc-extend-description-of-fib-expression.patch new file mode 100644 index 0000000..cde9463 --- /dev/null +++ b/0138-doc-extend-description-of-fib-expression.patch @@ -0,0 +1,142 @@ +From 3f60d652844c6bfab81b4b62ac228a869231eae7 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:10:26 +0200 +Subject: [PATCH] doc: extend description of fib expression + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit be4b61c05a2491aad596aa9243b17b13c937b347 + +commit be4b61c05a2491aad596aa9243b17b13c937b347 +Author: Florian Westphal +Date: Thu Oct 10 15:37:42 2024 +0200 + + doc: extend description of fib expression + + Describe the input keys and the result types. + Mention which input keys are mandatory and which keys are mutually + exclusive. + + Describe which hooks can be used with the various lookup modifiers + and extend the examples with more information on fib expression + capabilities. + + Closes: https://bugzilla.netfilter.org/show_bug.cgi?id=1663 + Signed-off-by: Florian Westphal + Reviewed-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + doc/primary-expression.txt | 77 +++++++++++++++++++++++++++++++------- + 1 file changed, 63 insertions(+), 14 deletions(-) + +diff --git a/doc/primary-expression.txt b/doc/primary-expression.txt +index e13970c..e85fa58 100644 +--- a/doc/primary-expression.txt ++++ b/doc/primary-expression.txt +@@ -304,17 +304,48 @@ table inet x { + FIB EXPRESSIONS + ~~~~~~~~~~~~~~~ + [verse] +-*fib* {*saddr* | *daddr* | *mark* | *iif* | *oif*} [*.* ...] {*oif* | *oifname* | *type*} ++*fib* 'FIB_TUPLE' 'FIB_RESULT' ++'FIB_TUPLE' := { *saddr* | *daddr*} [ *.* { *iif* | *oif* } *.* *mark* ] ++'FIB_RESULT' := { *oif* | *oifname* | *type* } + +-A fib expression queries the fib (forwarding information base) to obtain +-information such as the output interface index a particular address would use. +-The input is a tuple of elements that is used as input to the fib lookup +-functions. + +-.fib expression specific types ++A fib expression queries the fib (forwarding information base) to obtain information ++such as the output interface index. ++ ++The first arguments to the *fib* expression are the input keys to be passed to the fib lookup function. ++One of *saddr* or *daddr* is mandatory, they are also mutually exclusive. ++ ++*mark*, *iif* and *oif* keywords are optional modifiers to influence the search result, see ++the *FIB_TUPLE* keyword table below for a description. ++The *iif* and *oif* tuple keywords are also mutually exclusive. ++ ++The last argument to the *fib* expression is the desired result type. ++ ++*oif* asks to obtain the interface index that would be used to send packets to the packets source ++(*saddr* key) or destination (*daddr* key). If no routing entry is found, the returned interface ++index is 0. ++ ++*oifname* is like *oif*, but it fills the interface name instead. This is useful to check dynamic ++interfaces such as ppp devices. If no entry is found, an empty interface name is returned. ++ ++*type* returns the address type such as unicast or multicast. A complete list of supported ++address types can be shown with *nft* *describe* *fib_addrtype*. ++ ++.FIB_TUPLE keywords + [options="header"] + |================== +-|Keyword| Description| Type ++|flag| Description ++|daddr| Perform a normal route lookup: search fib for route to the *destination address* of the packet. ++|saddr| Perform a reverse route lookup: search the fib for route to the *source address* of the packet. ++|mark | consider the packet mark (nfmark) when querying the fib. ++|iif | if fib lookups provides a route then check its output interface is identical to the packets *input* interface. ++|oif | if fib lookups provides a route then check its output interface is identical to the packets *output* interface. This flag can only be used with the *type* result. ++|======================= ++ ++.FIB_RESULT keywords ++[options="header"] ++|================== ++|Keyword| Description| Result Type + |oif| + Output interface index| + integer (32 bit) +@@ -323,25 +354,43 @@ Output interface name| + string + |type| + Address type | +-fib_addrtype ++fib_addrtype (see *nft* *describe* *fib_addrtype* for a list) + |======================= + +-Use *nft* *describe* *fib_addrtype* to get a list of all address types. ++The *oif* and *oifname* result is only valid in the *prerouting*, *input* and *forward* hooks. ++The *type* can be queried from any one of *prerouting*, *input*, *forward* *output* and *postrouting*. ++ ++For *type*, the presence of the *iif* keyword in the 'FIB_TUPLE' modifiers restrict the available ++hooks to those where the packet is associated with an incoming interface, i.e. *prerouting*, *input* and *forward*. ++Likewise, the *oif* keyword in the 'FIB_TUPLE' modifier list will limit the available hooks to ++*forward*, *output* and *postrouting*. + + .Using fib expressions + ---------------------- + # drop packets without a reverse path + filter prerouting fib saddr . iif oif missing drop + +-In this example, 'saddr . iif' looks up routing information based on the source address and the input interface. +-oif picks the output interface index from the routing information. ++In this example, 'saddr . iif' looks up a route to the *source address* of the packet and restricts matching ++results to the interface that the packet arrived on, then stores the output interface index from the obtained ++fib route result. ++ + If no route was found for the source address/input interface combination, the output interface index is zero. +-In case the input interface is specified as part of the input key, the output interface index is always the same as the input interface index or zero. +-If only 'saddr oif' is given, then oif can be any interface index or zero. ++Hence, this rule will drop all packets that do not have a strict reverse path (hypothetical reply packet ++would be sent via the interface the tested packet arrived on). ++ ++If only 'saddr oif' is used as the input key, then this rule would only drop packets where the fib cannot ++find a route. In most setups this will never drop packets because the default route is returned. + +-# drop packets to address not configured on incoming interface ++# drop packets if the destination ip address is not configured on the incoming interface + filter prerouting fib daddr . iif type != { local, broadcast, multicast } drop + ++This queries the fib based on the current packets' destination address and the incoming interface. ++ ++If the packet is sent to a unicast address that is configured on a different interface, then the packet ++will be dropped as such an address would be classified as 'unicast' type. ++Without the 'iif' modifier, any address configured on the local machine is 'local', and unicast addresses ++not configured on any interface would return the type 'unicast'. ++ + # perform lookup in a specific 'blackhole' table (0xdead, needs ip appropriate ip rule) + filter prerouting meta mark set 0xdead fib daddr . mark type vmap { blackhole : drop, prohibit : jump prohibited, unreachable : drop } + ---------------------- diff --git a/0139-json-collapse-set-element-commands-from-parser.patch b/0139-json-collapse-set-element-commands-from-parser.patch new file mode 100644 index 0000000..2396ace --- /dev/null +++ b/0139-json-collapse-set-element-commands-from-parser.patch @@ -0,0 +1,83 @@ +From bc7037b73665ede562e1f369b1889f55d4fc0101 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:10:26 +0200 +Subject: [PATCH] json: collapse set element commands from parser + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 193faa5475a5df7d9ac0b1a8fe647196de3e5688 + +commit 193faa5475a5df7d9ac0b1a8fe647196de3e5688 +Author: Pablo Neira Ayuso +Date: Thu Oct 31 21:38:02 2024 +0100 + + json: collapse set element commands from parser + + Update json parser to collapse {add,create} element commands to reduce + memory consumption in the case of large sets defined by one element per + command: + + {"nftables": [{"add": {"element": {"family": "ip", "table": "x", "name": + "y", "elem": [{"set": ["1.1.0.0"]}]}}},...]} + + Add CTX_F_COLLAPSED flag to report that command has been collapsed. + + This patch reduces memory consumption by ~32% this case. + + Fixes: 20f1c60ac8c8 ("src: collapse set element commands from parser") + Reported-by: Eric Garver + Tested-by: Eric Garver + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/parser_json.c | 16 ++++++++++++++++ + 1 file changed, 16 insertions(+) + +diff --git a/src/parser_json.c b/src/parser_json.c +index f7ab79f..fc40d68 100644 +--- a/src/parser_json.c ++++ b/src/parser_json.c +@@ -18,6 +18,7 @@ + #include + #include + #include ++#include + #include + #include + +@@ -49,6 +50,7 @@ + #define CTX_F_SES (1 << 6) /* set_elem_expr_stmt */ + #define CTX_F_MAP (1 << 7) /* LHS of map_expr */ + #define CTX_F_CONCAT (1 << 8) /* inside concat_expr */ ++#define CTX_F_COLLAPSED (1 << 9) + + struct json_ctx { + struct nft_ctx *nft; +@@ -3465,6 +3467,15 @@ static struct cmd *json_parse_cmd_add_element(struct json_ctx *ctx, + handle_free(&h); + return NULL; + } ++ ++ if ((op == CMD_CREATE || op == CMD_ADD) && ++ nft_cmd_collapse_elems(op, ctx->cmds, &h, expr)) { ++ handle_free(&h); ++ expr_free(expr); ++ ctx->flags |= CTX_F_COLLAPSED; ++ return NULL; ++ } ++ + return cmd_alloc(op, cmd_obj, &h, int_loc, expr); + } + +@@ -4294,6 +4305,11 @@ static int __json_parse(struct json_ctx *ctx) + cmd = json_parse_cmd(ctx, value); + + if (!cmd) { ++ if (ctx->flags & CTX_F_COLLAPSED) { ++ ctx->flags &= ~CTX_F_COLLAPSED; ++ continue; ++ } ++ + json_error(ctx, "Parsing command array at index %zd failed.", index); + return -1; + } diff --git a/0140-json-Support-typeof-in-set-and-map-types.patch b/0140-json-Support-typeof-in-set-and-map-types.patch new file mode 100644 index 0000000..d022fa6 --- /dev/null +++ b/0140-json-Support-typeof-in-set-and-map-types.patch @@ -0,0 +1,137 @@ +From cebeb41f6844bccce4985023e57209a45ccfcbeb Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:10:26 +0200 +Subject: [PATCH] json: Support typeof in set and map types + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit bb6312484af93a83a9ec8716f3887a43566a775a +Conflicts: Dropped changes to non-existent .json-nft dumps + +commit bb6312484af93a83a9ec8716f3887a43566a775a +Author: Phil Sutter +Date: Sat Sep 28 00:55:34 2024 +0200 + + json: Support typeof in set and map types + + Implement this as a special "type" property value which is an object + with sole property "typeof". The latter's value is the JSON + representation of the expression in set->key, so for concatenated + typeofs it is a concat expression. + + All this is a bit clumsy right now but it works and it should be + possible to tear it down a bit for more user-friendliness in a + compatible way by either replacing the concat expression by the array it + contains or even the whole "typeof" object - the parser would just + assume any object (or objects in an array) in the "type" property value + are expressions to extract a type from. + + Signed-off-by: Phil Sutter + +Signed-off-by: Phil Sutter +--- + doc/libnftables-json.adoc | 7 ++++--- + src/json.c | 13 ++++++++++++- + src/parser_json.c | 9 +++++++++ + tests/monitor/testcases/map-expr.t | 2 +- + tests/monitor/testcases/set-concat-interval.t | 2 +- + 5 files changed, 27 insertions(+), 6 deletions(-) + +diff --git a/doc/libnftables-json.adoc b/doc/libnftables-json.adoc +index 1bdb5c5..b90a1fc 100644 +--- a/doc/libnftables-json.adoc ++++ b/doc/libnftables-json.adoc +@@ -341,7 +341,7 @@ ____ + "auto-merge":* 'BOOLEAN' + *}}* + +-'SET_TYPE' := 'STRING' | *[* 'SET_TYPE_LIST' *]* ++'SET_TYPE' := 'STRING' | *[* 'SET_TYPE_LIST' *]* | *{ "typeof":* 'EXPRESSION' *}* + 'SET_TYPE_LIST' := 'STRING' [*,* 'SET_TYPE_LIST' ] + 'SET_POLICY' := *"performance"* | *"memory"* + 'SET_FLAG_LIST' := 'SET_FLAG' [*,* 'SET_FLAG_LIST' ] +@@ -381,8 +381,9 @@ that they translate a unique key to a value. + Automatic merging of adjacent/overlapping set elements in interval sets. + + ==== TYPE +-The set type might be a string, such as *"ipv4_addr"* or an array +-consisting of strings (for concatenated types). ++The set type might be a string, such as *"ipv4_addr"*, an array ++consisting of strings (for concatenated types) or a *typeof* object containing ++an expression to extract the type from. + + ==== ELEM + A single set element might be given as string, integer or boolean value for +diff --git a/src/json.c b/src/json.c +index 90d9795..d9226e2 100644 +--- a/src/json.c ++++ b/src/json.c +@@ -87,6 +87,17 @@ static json_t *set_dtype_json(const struct expr *key) + return root; + } + ++static json_t *set_key_dtype_json(const struct set *set, ++ struct output_ctx *octx) ++{ ++ bool use_typeof = set->key_typeof_valid; ++ ++ if (!use_typeof) ++ return set_dtype_json(set->key); ++ ++ return json_pack("{s:o}", "typeof", expr_print_json(set->key, octx)); ++} ++ + static json_t *stmt_print_json(const struct stmt *stmt, struct output_ctx *octx) + { + char buf[1024]; +@@ -149,7 +160,7 @@ static json_t *set_print_json(struct output_ctx *octx, const struct set *set) + "family", family2str(set->handle.family), + "name", set->handle.set.name, + "table", set->handle.table.name, +- "type", set_dtype_json(set->key), ++ "type", set_key_dtype_json(set, octx), + "handle", set->handle.handle.id); + + if (set->comment) +diff --git a/src/parser_json.c b/src/parser_json.c +index fc40d68..ad589d7 100644 +--- a/src/parser_json.c ++++ b/src/parser_json.c +@@ -1706,7 +1706,16 @@ static struct expr *json_parse_dtype_expr(struct json_ctx *ctx, json_t *root) + compound_expr_add(expr, i); + } + return expr; ++ } else if (json_is_object(root)) { ++ const char *key; ++ json_t *val; ++ ++ if (!json_unpack_stmt(ctx, root, &key, &val) && ++ !strcmp(key, "typeof")) { ++ return json_parse_expr(ctx, val); ++ } + } ++ + json_error(ctx, "Invalid set datatype."); + return NULL; + } +diff --git a/tests/monitor/testcases/map-expr.t b/tests/monitor/testcases/map-expr.t +index 8729c0b..d11ad0e 100644 +--- a/tests/monitor/testcases/map-expr.t ++++ b/tests/monitor/testcases/map-expr.t +@@ -3,4 +3,4 @@ I add table ip t + I add map ip t m { typeof meta day . meta hour : verdict; flags interval; counter; } + O - + J {"add": {"table": {"family": "ip", "name": "t", "handle": 0}}} +-J {"add": {"map": {"family": "ip", "name": "m", "table": "t", "type": ["day", "hour"], "handle": 0, "map": "verdict", "flags": ["interval"], "stmt": [{"counter": null}]}}} ++J {"add": {"map": {"family": "ip", "name": "m", "table": "t", "type": {"typeof": {"concat": [{"meta": {"key": "day"}}, {"meta": {"key": "hour"}}]}}, "handle": 0, "map": "verdict", "flags": ["interval"], "stmt": [{"counter": null}]}}} +diff --git a/tests/monitor/testcases/set-concat-interval.t b/tests/monitor/testcases/set-concat-interval.t +index 75f3828..3542b82 100644 +--- a/tests/monitor/testcases/set-concat-interval.t ++++ b/tests/monitor/testcases/set-concat-interval.t +@@ -10,6 +10,6 @@ I add map ip t s { typeof udp length . @ih,32,32 : verdict; flags interval; elem + O add map ip t s { typeof udp length . @ih,32,32 : verdict; flags interval; } + O add element ip t s { 20-80 . 0x14 : accept } + O add element ip t s { 1-10 . 0xa : drop } +-J {"add": {"map": {"family": "ip", "name": "s", "table": "t", "type": ["integer", "integer"], "handle": 0, "map": "verdict", "flags": ["interval"]}}} ++J {"add": {"map": {"family": "ip", "name": "s", "table": "t", "type": {"typeof": {"concat": [{"payload": {"protocol": "udp", "field": "length"}}, {"payload": {"base": "ih", "offset": 32, "len": 32}}]}}, "handle": 0, "map": "verdict", "flags": ["interval"]}}} + J {"add": {"element": {"family": "ip", "table": "t", "name": "s", "elem": {"set": [[{"concat": [{"range": [20, 80]}, 20]}, {"accept": null}]]}}}} + J {"add": {"element": {"family": "ip", "table": "t", "name": "s", "elem": {"set": [[{"concat": [{"range": [1, 10]}, 10]}, {"drop": null}]]}}}} diff --git a/0141-tests-py-Fix-for-storing-payload-into-missing-file.patch b/0141-tests-py-Fix-for-storing-payload-into-missing-file.patch new file mode 100644 index 0000000..4c60ac1 --- /dev/null +++ b/0141-tests-py-Fix-for-storing-payload-into-missing-file.patch @@ -0,0 +1,43 @@ +From 6b16e1ba74ba42eccd7f39ad6408a1bb7f527b79 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:10:44 +0200 +Subject: [PATCH] tests: py: Fix for storing payload into missing file + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit c1c0c54e237c880adaa8172b93d7450e6c617cfc + +commit c1c0c54e237c880adaa8172b93d7450e6c617cfc +Author: Phil Sutter +Date: Wed Oct 2 19:55:49 2024 +0200 + + tests: py: Fix for storing payload into missing file + + When running a test for which no corresponding *.payload file exists, + the *.payload.got file name was incorrectly constructed due to + 'payload_path' variable not being set. + + Fixes: 2cfab7a3e10fc ("tests/py: Write dissenting payload into the right file") + Signed-off-by: Phil Sutter + +Signed-off-by: Phil Sutter +--- + tests/py/nft-test.py | 5 ++--- + 1 file changed, 2 insertions(+), 3 deletions(-) + +diff --git a/tests/py/nft-test.py b/tests/py/nft-test.py +index a7d27c2..90c7612 100755 +--- a/tests/py/nft-test.py ++++ b/tests/py/nft-test.py +@@ -769,10 +769,9 @@ def rule_add(rule, filename, lineno, force_all_family_option, filename_path): + + if rule[1].strip() == "ok": + payload_expected = None +- payload_path = None ++ payload_path = "%s.payload" % filename_path + try: +- payload_log = open("%s.payload" % filename_path) +- payload_path = payload_log.name ++ payload_log = open(payload_path) + payload_expected = payload_find_expected(payload_log, rule[0]) + except: + payload_log = None diff --git a/0142-optimize-compare-expression-length.patch b/0142-optimize-compare-expression-length.patch new file mode 100644 index 0000000..30898e4 --- /dev/null +++ b/0142-optimize-compare-expression-length.patch @@ -0,0 +1,61 @@ +From 673238f2b9afd60196bb5c4a3be0c3632ddf52d1 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:10:44 +0200 +Subject: [PATCH] optimize: compare expression length + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit bc0311378285d41850e3508df905d75959ba4239 + +commit bc0311378285d41850e3508df905d75959ba4239 +Author: Pablo Neira Ayuso +Date: Mon Nov 18 12:44:06 2024 +0100 + + optimize: compare expression length + + do not merge raw payload expressions with different length. + + Other expression rely on key comparison which is assumed to have the + same length already. + + Fixes: 60dcc01d6351 ("optimize: add __expr_cmp()") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/optimize.c | 2 ++ + .../testcases/optimizations/nomerge_raw_payload | 13 +++++++++++++ + 2 files changed, 15 insertions(+) + create mode 100755 tests/shell/testcases/optimizations/nomerge_raw_payload + +diff --git a/src/optimize.c b/src/optimize.c +index 9f0965c..8024590 100644 +--- a/src/optimize.c ++++ b/src/optimize.c +@@ -38,6 +38,8 @@ static bool __expr_cmp(const struct expr *expr_a, const struct expr *expr_b) + { + if (expr_a->etype != expr_b->etype) + return false; ++ if (expr_a->len != expr_b->len) ++ return false; + + switch (expr_a->etype) { + case EXPR_PAYLOAD: +diff --git a/tests/shell/testcases/optimizations/nomerge_raw_payload b/tests/shell/testcases/optimizations/nomerge_raw_payload +new file mode 100755 +index 0000000..bb8678a +--- /dev/null ++++ b/tests/shell/testcases/optimizations/nomerge_raw_payload +@@ -0,0 +1,13 @@ ++#!/bin/bash ++ ++set -e ++ ++RULESET="table ip x { ++ chain y { ++ type filter hook prerouting priority raw; policy accept; ++ @th,160,32 0x02736c00 drop comment \"sl\" ++ @th,160,112 0x870697a7a6173656f03636f6d00 drop comment \"pizzaseo.com\" ++ } ++}" ++ ++$NFT -o -f - <<< $RULESET diff --git a/0143-intervals-set-internal-element-location-with-the-del.patch b/0143-intervals-set-internal-element-location-with-the-del.patch new file mode 100644 index 0000000..89a57fd --- /dev/null +++ b/0143-intervals-set-internal-element-location-with-the-del.patch @@ -0,0 +1,115 @@ +From 7c41a3ed1b2920253acacee79644b3996bce8628 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:10:44 +0200 +Subject: [PATCH] intervals: set internal element location with the deletion + trigger + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 93077e35accccd8cc056b67f70bfb3182c819fd4 + +commit 93077e35accccd8cc056b67f70bfb3182c819fd4 +Author: Pablo Neira Ayuso +Date: Wed Dec 4 23:36:05 2024 +0100 + + intervals: set internal element location with the deletion trigger + + set location of internal elements (already in the kernel) to the one + that partial or fully deletes it. + + Otherwise, error reporting refers to internal location. + + Before this patch: + + # nft delete element x y { 1.1.1.3 } + Error: Could not process rule: Too many open files in system + delete element x y { 1.1.1.3 } + ^^^^^^^ + + After this patch: + + # nft delete element x y { 1.1.1.3 } + Error: Could not process rule: Too many open files in system + delete element x y { 1.1.1.3 } + ^^^^^^^ + + This occurs after splitting an existing interval in two: + + remove: [1010100-10101ff] + add: [1010100-1010102] + add: [1010104-10101ff] + + which results in two additions after removing the existing interval + that is split. + + Fixes: 81e36530fcac ("src: replace interval segment tree overlap and automerge") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/intervals.c | 11 ++++++++++- + 1 file changed, 10 insertions(+), 1 deletion(-) + +diff --git a/src/intervals.c b/src/intervals.c +index af1415a..e771aae 100644 +--- a/src/intervals.c ++++ b/src/intervals.c +@@ -81,6 +81,7 @@ static void remove_overlapping_range(struct set_automerge_ctx *ctx, + struct expr *prev, struct expr *i) + { + if (i->flags & EXPR_F_KERNEL) { ++ i->location = prev->location; + purge_elem(ctx, i); + return; + } +@@ -99,12 +100,14 @@ static bool merge_ranges(struct set_automerge_ctx *ctx, + struct range *prev_range, struct range *range) + { + if (prev->flags & EXPR_F_KERNEL) { ++ prev->location = i->location; + purge_elem(ctx, prev); + expr_free(i->key->left); + i->key->left = expr_get(prev->key->left); + mpz_set(prev_range->high, range->high); + return true; + } else if (i->flags & EXPR_F_KERNEL) { ++ i->location = prev->location; + purge_elem(ctx, i); + expr_free(prev->key->right); + prev->key->right = expr_get(i->key->right); +@@ -299,6 +302,7 @@ static void __adjust_elem_left(struct set *set, struct expr *prev, struct expr * + static void adjust_elem_left(struct set *set, struct expr *prev, struct expr *i, + struct expr *purge) + { ++ prev->location = i->location; + remove_elem(prev, set, purge); + __adjust_elem_left(set, prev, i); + +@@ -318,6 +322,7 @@ static void __adjust_elem_right(struct set *set, struct expr *prev, struct expr + static void adjust_elem_right(struct set *set, struct expr *prev, struct expr *i, + struct expr *purge) + { ++ prev->location = i->location; + remove_elem(prev, set, purge); + __adjust_elem_right(set, prev, i); + +@@ -330,6 +335,8 @@ static void split_range(struct set *set, struct expr *prev, struct expr *i, + { + struct expr *clone; + ++ prev->location = i->location; ++ + if (prev->flags & EXPR_F_KERNEL) { + clone = expr_clone(prev); + list_move_tail(&clone->list, &purge->expressions); +@@ -417,8 +424,10 @@ static int setelem_delete(struct list_head *msgs, struct set *set, + if (mpz_cmp(prev_range.low, range.low) == 0 && + mpz_cmp(prev_range.high, range.high) == 0) { + if (elem->flags & EXPR_F_REMOVE) { +- if (prev->flags & EXPR_F_KERNEL) ++ if (prev->flags & EXPR_F_KERNEL) { ++ prev->location = elem->location; + list_move_tail(&prev->list, &purge->expressions); ++ } + + list_del(&elem->list); + expr_free(elem); diff --git a/0144-parser_bison-fix-UaF-when-reporting-table-parse-erro.patch b/0144-parser_bison-fix-UaF-when-reporting-table-parse-erro.patch new file mode 100644 index 0000000..56d6b87 --- /dev/null +++ b/0144-parser_bison-fix-UaF-when-reporting-table-parse-erro.patch @@ -0,0 +1,46 @@ +From 82f4adf7dc43f9d63de11ff5e5765871b09b545f Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:10:44 +0200 +Subject: [PATCH] parser_bison: fix UaF when reporting table parse error + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 3ba0e5af6b5da9dfff5273bc1f0f15a60a9fe33b +Conflicts: Changes in removed code due to previous backports + +commit 3ba0e5af6b5da9dfff5273bc1f0f15a60a9fe33b +Author: Florian Westphal +Date: Tue Jan 7 23:55:06 2025 +0100 + + parser_bison: fix UaF when reporting table parse error + + It passed already-freed memory to erec function. Found with afl++ and asan. + + Fixes: 4955ae1a81b7 ("Add support for table's persist flag") + Signed-off-by: Florian Westphal + Reviewed-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/parser_bison.y | 4 +++- + 1 file changed, 3 insertions(+), 1 deletion(-) + +diff --git a/src/parser_bison.y b/src/parser_bison.y +index 9bfd464..fb91c3e 100644 +--- a/src/parser_bison.y ++++ b/src/parser_bison.y +@@ -1939,12 +1939,14 @@ table_flags : table_flag + table_flag : STRING + { + $$ = parse_table_flag($1); +- xfree($1); + if ($$ == 0) { + erec_queue(error(&@1, "unknown table option %s", $1), + state->msgs); ++ free_const($1); + YYERROR; + } ++ ++ free_const($1); + } + ; + diff --git a/0145-intervals-add-helper-function-to-set-previous-elemen.patch b/0145-intervals-add-helper-function-to-set-previous-elemen.patch new file mode 100644 index 0000000..36fb22d --- /dev/null +++ b/0145-intervals-add-helper-function-to-set-previous-elemen.patch @@ -0,0 +1,65 @@ +From 95a2b5535929c478daf8bb23300cfa2e752cb87f Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:11:00 +0200 +Subject: [PATCH] intervals: add helper function to set previous element + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 450590ab57e61ec8a300c67decbd0e850e576a81 + +commit 450590ab57e61ec8a300c67decbd0e850e576a81 +Author: Pablo Neira Ayuso +Date: Fri Jan 3 17:40:48 2025 +0100 + + intervals: add helper function to set previous element + + Add helper function to set previous element during the automerge + iteration. No functional changes are intended. + + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/intervals.c | 16 ++++++++++------ + 1 file changed, 10 insertions(+), 6 deletions(-) + +diff --git a/src/intervals.c b/src/intervals.c +index e771aae..a817cd0 100644 +--- a/src/intervals.c ++++ b/src/intervals.c +@@ -143,6 +143,14 @@ static void set_sort_splice(struct expr *init, struct set *set) + } + } + ++static void set_prev_elem(struct expr **prev, struct expr *i, ++ struct range *prev_range, struct range *range) ++{ ++ *prev = i; ++ mpz_set(prev_range->low, range->low); ++ mpz_set(prev_range->high, range->high); ++} ++ + static void setelem_automerge(struct set_automerge_ctx *ctx) + { + struct expr *i, *next, *prev = NULL; +@@ -163,9 +171,7 @@ static void setelem_automerge(struct set_automerge_ctx *ctx) + range_expr_value_high(range.high, i); + + if (!prev) { +- prev = i; +- mpz_set(prev_range.low, range.low); +- mpz_set(prev_range.high, range.high); ++ set_prev_elem(&prev, i, &prev_range, &range); + continue; + } + +@@ -187,9 +193,7 @@ static void setelem_automerge(struct set_automerge_ctx *ctx) + } + } + +- prev = i; +- mpz_set(prev_range.low, range.low); +- mpz_set(prev_range.high, range.high); ++ set_prev_elem(&prev, i, &prev_range, &range); + } + + mpz_clear(prev_range.low); diff --git a/0146-src-add-EXPR_RANGE_VALUE-expression-and-use-it.patch b/0146-src-add-EXPR_RANGE_VALUE-expression-and-use-it.patch new file mode 100644 index 0000000..e6ea88c --- /dev/null +++ b/0146-src-add-EXPR_RANGE_VALUE-expression-and-use-it.patch @@ -0,0 +1,439 @@ +From a1d0fd8692380bf37bcfc8e404537cffa6155adc Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:11:00 +0200 +Subject: [PATCH] src: add EXPR_RANGE_VALUE expression and use it + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit c9ee9032b0ee9e802f73dda90f5c84357aff8148 + +commit c9ee9032b0ee9e802f73dda90f5c84357aff8148 +Author: Pablo Neira Ayuso +Date: Fri Jan 3 17:40:54 2025 +0100 + + src: add EXPR_RANGE_VALUE expression and use it + + set element with range takes 4 instances of struct expr: + + EXPR_SET_ELEM -> EXPR_RANGE -> (2) EXPR_VALUE + + where EXPR_RANGE represents two references to struct expr with constant + value. + + This new EXPR_RANGE_VALUE trims it down to two expressions: + + EXPR_SET_ELEM -> EXPR_RANGE_VALUE + + with two direct low and high values that represent the range: + + struct { + mpz_t low; + mpz_t high; + }; + + this two new direct values in struct expr do not modify its size. + + setelem_expr_to_range() translates EXPR_RANGE to EXPR_RANGE_VALUE, this + conversion happens at a later stage. + + constant_range_expr_print() translates this structure to constant values + to reuse the existing datatype_print() which relies in singleton values. + + The automerge routine has been updated to use EXPR_RANGE_VALUE. + + This requires a follow up patch to rework the conversion from range + expression to singleton element to provide a noticeable memory + consumption reduction. + + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + include/expression.h | 13 +++++++ + src/expression.c | 83 +++++++++++++++++++++++++++++++++++++++++ + src/intervals.c | 88 ++++++++++++++++++++++---------------------- + src/mergesort.c | 2 + + 4 files changed, 143 insertions(+), 43 deletions(-) + +diff --git a/include/expression.h b/include/expression.h +index bf6df76..af9b0d5 100644 +--- a/include/expression.h ++++ b/include/expression.h +@@ -47,6 +47,7 @@ + * @EXPR_XFRM XFRM (ipsec) expression + * @EXPR_SET_ELEM_CATCHALL catchall element expression + * @EXPR_FLAGCMP flagcmp expression ++ * @EXPR_RANGE_VALUE constant range expression + */ + enum expr_types { + EXPR_INVALID, +@@ -79,6 +80,7 @@ enum expr_types { + EXPR_XFRM, + EXPR_SET_ELEM_CATCHALL, + EXPR_FLAGCMP, ++ EXPR_RANGE_VALUE, + + EXPR_MAX = EXPR_FLAGCMP + }; +@@ -276,6 +278,11 @@ struct expr { + /* EXPR_VALUE */ + mpz_t value; + }; ++ struct { ++ /* EXPR_RANGE_VALUE */ ++ mpz_t low; ++ mpz_t high; ++ } range; + struct { + /* EXPR_PREFIX */ + struct expr *prefix; +@@ -472,6 +479,12 @@ extern struct expr *constant_expr_join(const struct expr *e1, + const struct expr *e2); + extern struct expr *constant_expr_splice(struct expr *expr, unsigned int len); + ++extern struct expr *constant_range_expr_alloc(const struct location *loc, ++ const struct datatype *dtype, ++ enum byteorder byteorder, ++ unsigned int len, ++ mpz_t low, mpz_t high); ++ + extern struct expr *flag_expr_alloc(const struct location *loc, + const struct datatype *dtype, + enum byteorder byteorder, +diff --git a/src/expression.c b/src/expression.c +index 992f510..adfdf27 100644 +--- a/src/expression.c ++++ b/src/expression.c +@@ -462,6 +462,84 @@ struct expr *constant_expr_splice(struct expr *expr, unsigned int len) + return slice; + } + ++static void constant_range_expr_print_one(const struct expr *expr, ++ const mpz_t value, ++ struct output_ctx *octx) ++{ ++ unsigned int len = div_round_up(expr->len, BITS_PER_BYTE); ++ unsigned char data[len]; ++ struct expr *dummy; ++ ++ /* create dummy temporary constant expression to print range. */ ++ mpz_export_data(data, value, expr->byteorder, len); ++ dummy = constant_expr_alloc(&expr->location, expr->dtype, ++ expr->byteorder, expr->len, data); ++ expr_print(dummy, octx); ++ expr_free(dummy); ++} ++ ++static void constant_range_expr_print(const struct expr *expr, ++ struct output_ctx *octx) ++{ ++ unsigned int flags = octx->flags; ++ ++ /* similar to range_expr_print(). */ ++ octx->flags &= ~(NFT_CTX_OUTPUT_SERVICE | ++ NFT_CTX_OUTPUT_REVERSEDNS | ++ NFT_CTX_OUTPUT_GUID); ++ octx->flags |= NFT_CTX_OUTPUT_NUMERIC_ALL; ++ ++ constant_range_expr_print_one(expr, expr->range.low, octx); ++ nft_print(octx, "-"); ++ constant_range_expr_print_one(expr, expr->range.high, octx); ++ ++ octx->flags = flags; ++} ++ ++static bool constant_range_expr_cmp(const struct expr *e1, const struct expr *e2) ++{ ++ return expr_basetype(e1) == expr_basetype(e2) && ++ !mpz_cmp(e1->range.low, e2->range.low) && ++ !mpz_cmp(e1->range.high, e2->range.high); ++} ++ ++static void constant_range_expr_clone(struct expr *new, const struct expr *expr) ++{ ++ mpz_init_set(new->range.low, expr->range.low); ++ mpz_init_set(new->range.high, expr->range.high); ++} ++ ++static void constant_range_expr_destroy(struct expr *expr) ++{ ++ mpz_clear(expr->range.low); ++ mpz_clear(expr->range.high); ++} ++ ++static const struct expr_ops constant_range_expr_ops = { ++ .type = EXPR_RANGE_VALUE, ++ .name = "range_value", ++ .print = constant_range_expr_print, ++ .cmp = constant_range_expr_cmp, ++ .clone = constant_range_expr_clone, ++ .destroy = constant_range_expr_destroy, ++}; ++ ++struct expr *constant_range_expr_alloc(const struct location *loc, ++ const struct datatype *dtype, ++ enum byteorder byteorder, ++ unsigned int len, mpz_t low, mpz_t high) ++{ ++ struct expr *expr; ++ ++ expr = expr_alloc(loc, EXPR_RANGE_VALUE, dtype, byteorder, len); ++ expr->flags = EXPR_F_CONSTANT | EXPR_F_SINGLETON; ++ ++ mpz_init_set(expr->range.low, low); ++ mpz_init_set(expr->range.high, high); ++ ++ return expr; ++} ++ + /* + * Allocate a constant expression with a single bit set at position n. + */ +@@ -1460,6 +1538,8 @@ void range_expr_value_low(mpz_t rop, const struct expr *expr) + switch (expr->etype) { + case EXPR_VALUE: + return mpz_set(rop, expr->value); ++ case EXPR_RANGE_VALUE: ++ return mpz_set(rop, expr->range.low); + case EXPR_PREFIX: + return range_expr_value_low(rop, expr->prefix); + case EXPR_RANGE: +@@ -1480,6 +1560,8 @@ void range_expr_value_high(mpz_t rop, const struct expr *expr) + switch (expr->etype) { + case EXPR_VALUE: + return mpz_set(rop, expr->value); ++ case EXPR_RANGE_VALUE: ++ return mpz_set(rop, expr->range.high); + case EXPR_PREFIX: + range_expr_value_low(rop, expr->prefix); + assert(expr->len >= expr->prefix_len); +@@ -1531,6 +1613,7 @@ static const struct expr_ops *__expr_ops_by_type(enum expr_types etype) + case EXPR_XFRM: return &xfrm_expr_ops; + case EXPR_SET_ELEM_CATCHALL: return &set_elem_catchall_expr_ops; + case EXPR_FLAGCMP: return &flagcmp_expr_ops; ++ case EXPR_RANGE_VALUE: return &constant_range_expr_ops; + } + + return NULL; +diff --git a/src/intervals.c b/src/intervals.c +index a817cd0..825d9b5 100644 +--- a/src/intervals.c ++++ b/src/intervals.c +@@ -15,15 +15,24 @@ + + static void setelem_expr_to_range(struct expr *expr) + { +- unsigned char data[sizeof(struct in6_addr) * BITS_PER_BYTE]; +- struct expr *key, *value; ++ struct expr *key; + mpz_t rop; + + assert(expr->etype == EXPR_SET_ELEM); + + switch (expr->key->etype) { + case EXPR_SET_ELEM_CATCHALL: ++ case EXPR_RANGE_VALUE: ++ break; + case EXPR_RANGE: ++ key = constant_range_expr_alloc(&expr->location, ++ expr->key->dtype, ++ expr->key->byteorder, ++ expr->key->len, ++ expr->key->left->value, ++ expr->key->right->value); ++ expr_free(expr->key); ++ expr->key = key; + break; + case EXPR_PREFIX: + mpz_init(rop); +@@ -32,16 +41,13 @@ static void setelem_expr_to_range(struct expr *expr) + mpz_switch_byteorder(expr->key->prefix->value, expr->len / BITS_PER_BYTE); + + mpz_ior(rop, rop, expr->key->prefix->value); +- mpz_export_data(data, rop, expr->key->prefix->byteorder, +- expr->key->prefix->len / BITS_PER_BYTE); ++ key = constant_range_expr_alloc(&expr->location, ++ expr->key->dtype, ++ expr->key->byteorder, ++ expr->key->len, ++ expr->key->prefix->value, ++ rop); + mpz_clear(rop); +- value = constant_expr_alloc(&expr->location, +- expr->key->prefix->dtype, +- expr->key->prefix->byteorder, +- expr->key->prefix->len, data); +- key = range_expr_alloc(&expr->location, +- expr_get(expr->key->prefix), +- value); + expr_free(expr->key); + expr->key = key; + break; +@@ -49,9 +55,12 @@ static void setelem_expr_to_range(struct expr *expr) + if (expr_basetype(expr)->type == TYPE_STRING) + mpz_switch_byteorder(expr->key->value, expr->len / BITS_PER_BYTE); + +- key = range_expr_alloc(&expr->location, +- expr_clone(expr->key), +- expr_get(expr->key)); ++ key = constant_range_expr_alloc(&expr->location, ++ expr->key->dtype, ++ expr->key->byteorder, ++ expr->key->len, ++ expr->key->value, ++ expr->key->value); + expr_free(expr->key); + expr->key = key; + break; +@@ -71,8 +80,8 @@ static void purge_elem(struct set_automerge_ctx *ctx, struct expr *i) + { + if (ctx->debug_mask & NFT_DEBUG_SEGTREE) { + pr_gmp_debug("remove: [%Zx-%Zx]\n", +- i->key->left->value, +- i->key->right->value); ++ i->key->range.low, ++ i->key->range.high); + } + list_move_tail(&i->list, &ctx->purge->expressions); + } +@@ -102,19 +111,16 @@ static bool merge_ranges(struct set_automerge_ctx *ctx, + if (prev->flags & EXPR_F_KERNEL) { + prev->location = i->location; + purge_elem(ctx, prev); +- expr_free(i->key->left); +- i->key->left = expr_get(prev->key->left); ++ mpz_set(i->key->range.low, prev->key->range.low); + mpz_set(prev_range->high, range->high); + return true; + } else if (i->flags & EXPR_F_KERNEL) { + i->location = prev->location; + purge_elem(ctx, i); +- expr_free(prev->key->right); +- prev->key->right = expr_get(i->key->right); ++ mpz_set(prev->key->range.high, i->key->range.high); + mpz_set(prev_range->high, range->high); + } else { +- expr_free(prev->key->right); +- prev->key->right = expr_get(i->key->right); ++ mpz_set(prev->key->range.high, i->key->range.high); + mpz_set(prev_range->high, range->high); + list_del(&i->list); + expr_free(i); +@@ -263,7 +269,7 @@ int set_automerge(struct list_head *msgs, struct cmd *cmd, struct set *set, + } else if (existing_set) { + if (debug_mask & NFT_DEBUG_SEGTREE) { + pr_gmp_debug("add: [%Zx-%Zx]\n", +- i->key->left->value, i->key->right->value); ++ i->key->range.low, i->key->range.high); + } + clone = expr_clone(i); + clone->flags |= EXPR_F_KERNEL; +@@ -297,9 +303,8 @@ static void remove_elem(struct expr *prev, struct set *set, struct expr *purge) + static void __adjust_elem_left(struct set *set, struct expr *prev, struct expr *i) + { + prev->flags &= ~EXPR_F_KERNEL; +- expr_free(prev->key->left); +- prev->key->left = expr_get(i->key->right); +- mpz_add_ui(prev->key->left->value, prev->key->left->value, 1); ++ mpz_set(prev->key->range.low, i->key->range.high); ++ mpz_add_ui(prev->key->range.low, prev->key->range.low, 1); + list_move(&prev->list, &set->existing_set->init->expressions); + } + +@@ -317,9 +322,8 @@ static void adjust_elem_left(struct set *set, struct expr *prev, struct expr *i, + static void __adjust_elem_right(struct set *set, struct expr *prev, struct expr *i) + { + prev->flags &= ~EXPR_F_KERNEL; +- expr_free(prev->key->right); +- prev->key->right = expr_get(i->key->left); +- mpz_sub_ui(prev->key->right->value, prev->key->right->value, 1); ++ mpz_set(prev->key->range.high, i->key->range.low); ++ mpz_sub_ui(prev->key->range.high, prev->key->range.high, 1); + list_move(&prev->list, &set->existing_set->init->expressions); + } + +@@ -348,14 +352,12 @@ static void split_range(struct set *set, struct expr *prev, struct expr *i, + + prev->flags &= ~EXPR_F_KERNEL; + clone = expr_clone(prev); +- expr_free(clone->key->left); +- clone->key->left = expr_get(i->key->right); +- mpz_add_ui(clone->key->left->value, i->key->right->value, 1); ++ mpz_set(clone->key->range.low, i->key->range.high); ++ mpz_add_ui(clone->key->range.low, i->key->range.high, 1); + list_add_tail(&clone->list, &set->existing_set->init->expressions); + +- expr_free(prev->key->right); +- prev->key->right = expr_get(i->key->left); +- mpz_sub_ui(prev->key->right->value, i->key->left->value, 1); ++ mpz_set(prev->key->range.high, i->key->range.low); ++ mpz_sub_ui(prev->key->range.high, i->key->range.low, 1); + list_move(&prev->list, &set->existing_set->init->expressions); + + list_del(&i->list); +@@ -528,13 +530,13 @@ int set_delete(struct list_head *msgs, struct cmd *cmd, struct set *set, + if (debug_mask & NFT_DEBUG_SEGTREE) { + list_for_each_entry(i, &init->expressions, list) + pr_gmp_debug("remove: [%Zx-%Zx]\n", +- i->key->left->value, i->key->right->value); ++ i->key->range.low, i->key->range.high); + list_for_each_entry(i, &add->expressions, list) + pr_gmp_debug("add: [%Zx-%Zx]\n", +- i->key->left->value, i->key->right->value); ++ i->key->range.low, i->key->range.high); + list_for_each_entry(i, &existing_set->init->expressions, list) + pr_gmp_debug("existing: [%Zx-%Zx]\n", +- i->key->left->value, i->key->right->value); ++ i->key->range.low, i->key->range.high); + } + + if (list_empty(&add->expressions)) { +@@ -689,7 +691,7 @@ int set_to_intervals(const struct set *set, struct expr *init, bool add) + continue; + + if (!prev && segtree_needs_first_segment(set, init, add) && +- mpz_cmp_ui(elem->key->left->value, 0)) { ++ mpz_cmp_ui(elem->key->range.low, 0)) { + mpz_set_ui(p, 0); + expr = constant_expr_alloc(&internal_location, + set->key->dtype, +@@ -713,15 +715,15 @@ int set_to_intervals(const struct set *set, struct expr *init, bool add) + mpz_switch_byteorder(p, set->key->len / BITS_PER_BYTE); + + if (!(set->flags & NFT_SET_ANONYMOUS) || +- mpz_cmp(p, elem->key->left->value) != 0) ++ mpz_cmp(p, elem->key->range.low) != 0) + list_add_tail(&newelem->list, &intervals); + else + expr_free(newelem); + } + newelem = NULL; + +- if (mpz_scan0(elem->key->right->value, 0) != set->key->len) { +- mpz_add_ui(p, elem->key->right->value, 1); ++ if (mpz_scan0(elem->key->range.high, 0) != set->key->len) { ++ mpz_add_ui(p, elem->key->range.high, 1); + expr = constant_expr_alloc(&elem->key->location, set->key->dtype, + set->key->byteorder, set->key->len, + NULL); +@@ -743,7 +745,7 @@ int set_to_intervals(const struct set *set, struct expr *init, bool add) + expr = constant_expr_alloc(&elem->key->location, set->key->dtype, + set->key->byteorder, set->key->len, NULL); + +- mpz_set(expr->value, elem->key->left->value); ++ mpz_set(expr->value, elem->key->range.low); + if (set->key->byteorder == BYTEORDER_HOST_ENDIAN) + mpz_switch_byteorder(expr->value, set->key->len / BITS_PER_BYTE); + +diff --git a/src/mergesort.c b/src/mergesort.c +index 5e676be..0452d60 100644 +--- a/src/mergesort.c ++++ b/src/mergesort.c +@@ -38,6 +38,8 @@ static mpz_srcptr expr_msort_value(const struct expr *expr, mpz_t value) + return expr_msort_value(expr->left, value); + case EXPR_VALUE: + return expr->value; ++ case EXPR_RANGE_VALUE: ++ return expr->range.low; + case EXPR_CONCAT: + concat_expr_msort_value(expr, value); + break; diff --git a/0147-intervals-do-not-merge-intervals-with-different-time.patch b/0147-intervals-do-not-merge-intervals-with-different-time.patch new file mode 100644 index 0000000..f2efcae --- /dev/null +++ b/0147-intervals-do-not-merge-intervals-with-different-time.patch @@ -0,0 +1,49 @@ +From 93282a1c744ea34c7d4dca6c6b918911eae1670e Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:11:00 +0200 +Subject: [PATCH] intervals: do not merge intervals with different timeout + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit da0bac050c8b2588242727f9915a1ea8bc48ceb2 + +commit da0bac050c8b2588242727f9915a1ea8bc48ceb2 +Author: Pablo Neira Ayuso +Date: Fri Jan 3 17:40:52 2025 +0100 + + intervals: do not merge intervals with different timeout + + If timeout/expiration of contiguous intervals is different, then do not + merge them. + + Fixes: 81e36530fcac ("src: replace interval segment tree overlap and automerge") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/intervals.c | 6 +++++- + 1 file changed, 5 insertions(+), 1 deletion(-) + +diff --git a/src/intervals.c b/src/intervals.c +index 825d9b5..0618b49 100644 +--- a/src/intervals.c ++++ b/src/intervals.c +@@ -157,6 +157,8 @@ static void set_prev_elem(struct expr **prev, struct expr *i, + mpz_set(prev_range->high, range->high); + } + ++static struct expr *interval_expr_key(struct expr *i); ++ + static void setelem_automerge(struct set_automerge_ctx *ctx) + { + struct expr *i, *next, *prev = NULL; +@@ -176,7 +178,9 @@ static void setelem_automerge(struct set_automerge_ctx *ctx) + range_expr_value_low(range.low, i); + range_expr_value_high(range.high, i); + +- if (!prev) { ++ if (!prev || ++ interval_expr_key(prev)->timeout != interval_expr_key(i)->timeout || ++ interval_expr_key(prev)->expiration != interval_expr_key(i)->expiration) { + set_prev_elem(&prev, i, &prev_range, &range); + continue; + } diff --git a/0148-evaluate-remove-variable-shadowing.patch b/0148-evaluate-remove-variable-shadowing.patch new file mode 100644 index 0000000..620311e --- /dev/null +++ b/0148-evaluate-remove-variable-shadowing.patch @@ -0,0 +1,38 @@ +From 94f803c8e0c50068cc6dc9e24e0baf5c5ffe7e29 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:11:00 +0200 +Subject: [PATCH] evaluate: remove variable shadowing + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 7a41d031b68d2e3b0e1cd64d4266244bbecc2c96 + +commit 7a41d031b68d2e3b0e1cd64d4266244bbecc2c96 +Author: Pablo Neira Ayuso +Date: Mon Jan 13 17:28:19 2025 +0100 + + evaluate: remove variable shadowing + + unsigned int i is already declared in resolve_ll_protocol_conflict(), + remove it. + + Fixes: 3a734d608131 ("evaluate: don't assert on net/transport header conflict") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/evaluate.c | 2 -- + 1 file changed, 2 deletions(-) + +diff --git a/src/evaluate.c b/src/evaluate.c +index 32d4c75..33f5724 100644 +--- a/src/evaluate.c ++++ b/src/evaluate.c +@@ -826,8 +826,6 @@ static int resolve_ll_protocol_conflict(struct eval_ctx *ctx, + return -1; + } + } else { +- unsigned int i; +- + /* payload desc stored in the L2 header stack? No conflict. */ + for (i = 0; i < pctx->stacked_ll_count; i++) { + if (pctx->stacked_ll[i] == payload->payload.desc) diff --git a/0149-netlink_delinarize-fix-bogus-munging-of-mask-value.patch b/0149-netlink_delinarize-fix-bogus-munging-of-mask-value.patch new file mode 100644 index 0000000..c773b52 --- /dev/null +++ b/0149-netlink_delinarize-fix-bogus-munging-of-mask-value.patch @@ -0,0 +1,173 @@ +From a22346b84b821a6726685dbdaa20c8d6f3be3905 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:11:00 +0200 +Subject: [PATCH] netlink_delinarize: fix bogus munging of mask value + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 44c803015a1e0bca54fb7b92fdc154d162f9dbfd + +commit 44c803015a1e0bca54fb7b92fdc154d162f9dbfd +Author: Florian Westphal +Date: Thu Jan 30 18:47:12 2025 +0100 + + netlink_delinarize: fix bogus munging of mask value + + Given following input: + table ip t { + chain c { + @ih,58,6 set 0 @ih,86,6 set 0 @ih,170,22 set 0 + } + } + + nft will produce following output: + chain c { + @ih,48,16 set @ih,48,16 & 0x3f @ih,80,16 set @ih,80,16 & 0x3f0 @ih,160,32 set @ih,160,32 & 0x3fffff + } + + The input side is correct, the generated expressions sent to kernel are: + + 1 [ payload load 2b @ inner header + 6 => reg 1 ] + 2 [ bitwise reg 1 = ( reg 1 & 0x0000c0ff ) ^ 0x00000000 ] + 3 [ payload write reg 1 => 2b @ inner header + 6 .. ] + 4 [ payload load 2b @ inner header + 10 => reg 1 ] + 5 [ bitwise reg 1 = ( reg 1 & 0x00000ffc ) ^ 0x00000000 ] + 6 [ payload write reg 1 => 2b @ inner header + 10 .. ] + 7 [ payload load 4b @ inner header + 20 => reg 1 ] + 8 [ bitwise reg 1 = ( reg 1 & 0x0000c0ff ) ^ 0x00000000 ] + 9 [ payload write reg 1 => 4b @ inner header + 20 .. ] + + @ih,58,6 set 0 <- Zero 6 bits, starting with bit 58 + + Changes to inner header mandate a checksum update, which only works for + even byte counts (except for last byte in the payload). + + Thus, we load 2b at offet 6. (16bits, offset 48). + + Because we want to zero 6 bits, we need a mask that retains 10 bits and + clears 6: b1111111111000000 (first 8 bit retains 48-57, last 6 bit clear + 58-63). The '0xc0ff' is not correct, but thats because debug output comes + from libnftnl which prints values in host byte order, the value will be + interpreted as big endian on kernel side, so this will do the right thing. + + Next, same problem: + + @ih,86,6 set 0 <- Zero 6 bits, starting with bit 86. + + nft needs to round down to even-sized byte offset, 10, then retain first + 6 bits (80 + 6 == 86), then clear 6 bits (86-91), then keep 4 more as-is + (92-95). + + So mask is 0xfc0f (in big endian) would be correct (b1111110000001111). + + Last expression, @ih,170,22 set 0, asks to clear 22 bits starting with bit + 170, nft correctly rounds this down to a 32 bit read at offset 160. + + Required mask keeps first 10 bits, then clears 22 + (b11111111110000000000000000000000). Required mask would be 0xffc00000, + which corresponds to the wrong-endian-printed value in line 8 above. + + Now that we convinced ourselves that the input side is correct, fix up + netlink delinearize to undo the mask alterations if we can't find a + template to print a human-readable payload expression. + + With this patch, we get this output: + + @ih,48,16 set @ih,48,16 & 0xffc0 @ih,80,16 set @ih,80,16 & 0xfc0f @ih,160,32 set @ih,160,32 & 0xffc00000 + + ... which isn't ideal. We should fixup the payload expression to display + the same output as the input, i.e. adjust payload->len and offset as per + mask and discard the mask instead. + + This will be done in a followup patch. + + Fixes: 50ca788ca4d0 ("netlink: decode payload statment") + Reported-by: Sunny73Cr + Signed-off-by: Florian Westphal + Reviewed-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/netlink_delinearize.c | 57 ++++++++++++++++++++++++--------------- + 1 file changed, 35 insertions(+), 22 deletions(-) + +diff --git a/src/netlink_delinearize.c b/src/netlink_delinearize.c +index 0ea10ee..95bc7a3 100644 +--- a/src/netlink_delinearize.c ++++ b/src/netlink_delinearize.c +@@ -3174,41 +3174,54 @@ static void stmt_payload_binop_postprocess(struct rule_pp_ctx *ctx) + break; + } + case EXPR_PAYLOAD: /* II? */ +- value = expr->right; +- if (value->etype != EXPR_VALUE) ++ payload = expr->left; ++ mask = expr->right; ++ ++ if (mask->etype != EXPR_VALUE) ++ return; ++ ++ if (!payload_expr_cmp(stmt->payload.expr, payload)) + return; + + switch (expr->op) { +- case OP_AND: /* IIa */ +- payload = expr->left; ++ case OP_AND: { /* IIa */ ++ mpz_t tmp; ++ ++ mpz_init(tmp); ++ mpz_set(tmp, mask->value); ++ + mpz_init_bitmask(bitmask, payload->len); +- mpz_xor(bitmask, bitmask, value->value); +- mpz_set(value->value, bitmask); ++ mpz_xor(bitmask, bitmask, mask->value); ++ mpz_set(mask->value, bitmask); + mpz_clear(bitmask); +- break; +- case OP_OR: /* IIb */ +- break; +- default: /* No idea */ +- return; +- } + +- stmt_payload_binop_pp(ctx, expr); +- if (!payload_is_known(expr->left)) +- return; ++ stmt_payload_binop_pp(ctx, expr); ++ if (!payload_is_known(expr->left)) { ++ mpz_set(mask->value, tmp); ++ mpz_clear(tmp); ++ return; ++ } + +- expr_free(stmt->payload.expr); ++ mpz_clear(tmp); + +- switch (expr->op) { +- case OP_AND: +- /* Mask was used to match payload, i.e. +- * user asked to set zero value. ++ /* Mask was used to match payload, i.e. user asked to ++ * clear the payload expression. ++ * The "mask" value becomes new stmt->payload.value ++ * so set this to 0. + */ +- mpz_set_ui(value->value, 0); ++ mpz_set_ui(mask->value, 0); + break; +- default: ++ } ++ case OP_OR: /* IIb */ ++ stmt_payload_binop_pp(ctx, expr); ++ if (!payload_is_known(expr->left)) ++ return; + break; ++ default: /* No idea what to do */ ++ return; + } + ++ expr_free(stmt->payload.expr); + stmt->payload.expr = expr_get(expr->left); + stmt->payload.val = expr_get(expr->right); + expr_free(expr); diff --git a/0150-ipopt-use-ipv4-address-datatype-for-address-field-in.patch b/0150-ipopt-use-ipv4-address-datatype-for-address-field-in.patch new file mode 100644 index 0000000..d51eda7 --- /dev/null +++ b/0150-ipopt-use-ipv4-address-datatype-for-address-field-in.patch @@ -0,0 +1,67 @@ +From ce85f29a3a4297546e29f6558b174814db3d91e6 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:11:00 +0200 +Subject: [PATCH] ipopt: use ipv4 address datatype for address field in ip + options + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 5faccb0681acb3b0175c4190eeaecf62f0bd12d4 + +commit 5faccb0681acb3b0175c4190eeaecf62f0bd12d4 +Author: Pablo Neira Ayuso +Date: Thu Jan 30 19:39:20 2025 +0100 + + ipopt: use ipv4 address datatype for address field in ip options + + So user does not have to play integer arithmetics to match on IPv4 + address. + + Before: + + # nft describe ip option lsrr addr + exthdr expression, datatype integer (integer), 32 bits + + After: + + # nft describe ip option lsrr addr + exthdr expression, datatype ipv4_addr (IPv4 address) (basetype integer), 32 bits + + Fixes: 226a0e072d5c ("exthdr: add support for matching IPv4 options") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/ipopt.c | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +diff --git a/src/ipopt.c b/src/ipopt.c +index 37f779d..c03a804 100644 +--- a/src/ipopt.c ++++ b/src/ipopt.c +@@ -24,7 +24,7 @@ static const struct exthdr_desc ipopt_lsrr = { + [IPOPT_FIELD_TYPE] = PHT("type", 0, 8), + [IPOPT_FIELD_LENGTH] = PHT("length", 8, 8), + [IPOPT_FIELD_PTR] = PHT("ptr", 16, 8), +- [IPOPT_FIELD_ADDR_0] = PHT("addr", 24, 32), ++ [IPOPT_FIELD_ADDR_0] = PROTO_HDR_TEMPLATE("addr", &ipaddr_type, BYTEORDER_BIG_ENDIAN, 24, 32), + }, + }; + +@@ -35,7 +35,7 @@ static const struct exthdr_desc ipopt_rr = { + [IPOPT_FIELD_TYPE] = PHT("type", 0, 8), + [IPOPT_FIELD_LENGTH] = PHT("length", 8, 8), + [IPOPT_FIELD_PTR] = PHT("ptr", 16, 8), +- [IPOPT_FIELD_ADDR_0] = PHT("addr", 24, 32), ++ [IPOPT_FIELD_ADDR_0] = PROTO_HDR_TEMPLATE("addr", &ipaddr_type, BYTEORDER_BIG_ENDIAN, 24, 32), + }, + }; + +@@ -46,7 +46,7 @@ static const struct exthdr_desc ipopt_ssrr = { + [IPOPT_FIELD_TYPE] = PHT("type", 0, 8), + [IPOPT_FIELD_LENGTH] = PHT("length", 8, 8), + [IPOPT_FIELD_PTR] = PHT("ptr", 16, 8), +- [IPOPT_FIELD_ADDR_0] = PHT("addr", 24, 32), ++ [IPOPT_FIELD_ADDR_0] = PROTO_HDR_TEMPLATE("addr", &ipaddr_type, BYTEORDER_BIG_ENDIAN, 24, 32), + }, + }; + diff --git a/0151-tests-shell-delete-netdev-chain-after-test.patch b/0151-tests-shell-delete-netdev-chain-after-test.patch new file mode 100644 index 0000000..de945ae --- /dev/null +++ b/0151-tests-shell-delete-netdev-chain-after-test.patch @@ -0,0 +1,47 @@ +From b7ee6c56581db65ea0937f79221cc37e360dbebd Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:11:00 +0200 +Subject: [PATCH] tests: shell: delete netdev chain after test + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 877255e8cfa10280f61218c8b1b4b6b7afb0e328 + +commit 877255e8cfa10280f61218c8b1b4b6b7afb0e328 +Author: Pablo Neira Ayuso +Date: Wed Jan 15 23:41:24 2025 +0100 + + tests: shell: delete netdev chain after test + + This update is needed for kernel patch: + + ("netfilter: nf_tables: Tolerate chains with no remaining hooks") + + otherwise this hits DUMP FAILED in newer kernels. + + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + tests/shell/testcases/chains/netdev_chain_0 | 1 + + tests/shell/testcases/json/netdev | 2 ++ + 2 files changed, 3 insertions(+) + +diff --git a/tests/shell/testcases/chains/netdev_chain_0 b/tests/shell/testcases/chains/netdev_chain_0 +index a323e6e..f2eae6a 100755 +--- a/tests/shell/testcases/chains/netdev_chain_0 ++++ b/tests/shell/testcases/chains/netdev_chain_0 +@@ -27,3 +27,4 @@ $NFT -f - <<< "$RULESET" + $NFT add chain netdev x y '{ devices = { d0 }; }' + $NFT add chain netdev x y '{ devices = { d1, d2, lo }; }' + $NFT delete chain netdev x y '{ devices = { lo }; }' ++$NFT delete chain netdev x y +diff --git a/tests/shell/testcases/json/netdev b/tests/shell/testcases/json/netdev +index 8c16cf4..23776c3 100755 +--- a/tests/shell/testcases/json/netdev ++++ b/tests/shell/testcases/json/netdev +@@ -26,3 +26,5 @@ if [ "$NFT_TEST_HAVE_json" = n ]; then + echo "Test partially skipped due to missing JSON support." + exit 77 + fi ++ ++$NFT delete chain netdev test_table test_chain diff --git a/0152-datatype-clamp-boolean-value-to-0-and-1.patch b/0152-datatype-clamp-boolean-value-to-0-and-1.patch new file mode 100644 index 0000000..f56f876 --- /dev/null +++ b/0152-datatype-clamp-boolean-value-to-0-and-1.patch @@ -0,0 +1,80 @@ +From 2964a54e202b5609ee0fd1f707c81a31264ac1cd Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:11:00 +0200 +Subject: [PATCH] datatype: clamp boolean value to 0 and 1 + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit afb6a8e66a11178cbdbfc152c4aa9dda961b2140 + +commit afb6a8e66a11178cbdbfc152c4aa9dda961b2140 +Author: Pablo Neira Ayuso +Date: Fri Jan 31 12:54:32 2025 +0100 + + datatype: clamp boolean value to 0 and 1 + + If user provides a numeric value larger than 0 or 1, match never + happens: + + # nft --debug=netlink add rule x y tcp option sack-perm 4 + ip x y + [ exthdr load tcpopt 1b @ 4 + 0 present => reg 1 ] + [ cmp eq reg 1 0x00000004 ] + + After this update: + + # nft --debug=netlink add rule x y tcp option sack-perm 4 + ip x y + [ exthdr load tcpopt 1b @ 4 + 0 present => reg 1 ] + [ cmp eq reg 1 0x00000001 ] + + This is to address a rare corner case, in case user specifies the + boolean value through the integer base type. + + Fixes: 9fd9baba43c8 ("Introduce boolean datatype and boolean expression") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/datatype.c | 24 ++++++++++++++++++++++++ + 1 file changed, 24 insertions(+) + +diff --git a/src/datatype.c b/src/datatype.c +index c94c839..6f73dd3 100644 +--- a/src/datatype.c ++++ b/src/datatype.c +@@ -1480,11 +1480,35 @@ static const struct symbol_table boolean_tbl = { + }, + }; + ++static struct error_record *boolean_type_parse(struct parse_ctx *ctx, ++ const struct expr *sym, ++ struct expr **res) ++{ ++ struct error_record *erec; ++ int num; ++ ++ erec = integer_type_parse(ctx, sym, res); ++ if (erec) ++ return erec; ++ ++ if (mpz_cmp_ui((*res)->value, 0)) ++ num = 1; ++ else ++ num = 0; ++ ++ expr_free(*res); ++ ++ *res = constant_expr_alloc(&sym->location, &boolean_type, ++ BYTEORDER_HOST_ENDIAN, 1, &num); ++ return NULL; ++} ++ + const struct datatype boolean_type = { + .type = TYPE_BOOLEAN, + .name = "boolean", + .desc = "boolean type", + .size = 1, ++ .parse = boolean_type_parse, + .basetype = &integer_type, + .sym_tbl = &boolean_tbl, + .json = boolean_type_json, diff --git a/0153-parser_bison-turn-redundant-ip-option-type-field-mat.patch b/0153-parser_bison-turn-redundant-ip-option-type-field-mat.patch new file mode 100644 index 0000000..e319523 --- /dev/null +++ b/0153-parser_bison-turn-redundant-ip-option-type-field-mat.patch @@ -0,0 +1,76 @@ +From 97bef0375709f9ec7ad2c9ac5ecb39d010dd0773 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:11:00 +0200 +Subject: [PATCH] parser_bison: turn redundant ip option type field match into + boolean + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit f9a48ce2f9c252bf74d98d10412b1f72585a45ec + +commit f9a48ce2f9c252bf74d98d10412b1f72585a45ec +Author: Pablo Neira Ayuso +Date: Fri Jan 31 11:14:22 2025 +0100 + + parser_bison: turn redundant ip option type field match into boolean + + The ip option expression allows for non-sense matching like: + + ip option lsrr type 1 + + because 'lsrr' already provides the type field, this never results in a + matching. + + Turn this expression into: + + ip option lsrr exists + + And update documentation to hide this redundant type field. + + Fixes: 226a0e072d5c ("exthdr: add support for matching IPv4 options") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + doc/payload-expression.txt | 8 ++++---- + src/parser_bison.y | 3 +++ + 2 files changed, 7 insertions(+), 4 deletions(-) + +diff --git a/doc/payload-expression.txt b/doc/payload-expression.txt +index 8afeb71..d648785 100644 +--- a/doc/payload-expression.txt ++++ b/doc/payload-expression.txt +@@ -845,16 +845,16 @@ TCP option matching also supports raw expression syntax to access arbitrary opti + |Keyword| Description | IP option fields + |lsrr| + Loose Source Route | +-type, length, ptr, addr ++length, ptr, addr + |ra| + Router Alert | +-type, length, value ++length, value + |rr| + Record Route | +-type, length, ptr, addr ++length, ptr, addr + |ssrr| + Strict Source Route | +-type, length, ptr, addr ++length, ptr, addr + |============================ + + .finding TCP options +diff --git a/src/parser_bison.y b/src/parser_bison.y +index fb91c3e..6435e8e 100644 +--- a/src/parser_bison.y ++++ b/src/parser_bison.y +@@ -5636,6 +5636,9 @@ ip_hdr_expr : IP ip_hdr_field close_scope_ip + erec_queue(error(&@1, "unknown ip option type/field"), state->msgs); + YYERROR; + } ++ ++ if ($4 == IPOPT_FIELD_TYPE) ++ $$->exthdr.flags = NFT_EXTHDR_F_PRESENT; + } + | IP OPTION ip_option_type close_scope_ip + { diff --git a/0154-parser_bison-compact-and-simplify-list-and-reset-syn.patch b/0154-parser_bison-compact-and-simplify-list-and-reset-syn.patch new file mode 100644 index 0000000..0a162ff --- /dev/null +++ b/0154-parser_bison-compact-and-simplify-list-and-reset-syn.patch @@ -0,0 +1,282 @@ +From 46e8c222f8d1faeeb495e34495fdcc59633a8eaf Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:11:00 +0200 +Subject: [PATCH] parser_bison: compact and simplify list and reset syntax + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit b15854ef81b4c22e4660d3876384f375554887b2 + +commit b15854ef81b4c22e4660d3876384f375554887b2 +Author: Florian Westphal +Date: Thu Jan 16 09:32:01 2025 +0100 + + parser_bison: compact and simplify list and reset syntax + + Works: + list sets + list sets inet + list sets table inet foo + + Doesn't work: + list sets inet foo + + Same for "list counters", "list quotas", etc. + + "reset" keyword however supports this: + reset counters inet foo + + and aliased this to + reset counters table inet foo + + This is inconsistent and not inuitive. + + Moreover, unlike "list sets", "list maps" only supported "list maps" and + "list maps inet", without the ability to only list maps of a given table. + + Compact this to unify the syntax so it becomes possible to omit the "table" + keyword for either reset or list mode. + + flowtables, secmarks and synproxys keywords are updated too. "flow table" + and "meters" are NOT changed since both of these are deprecated in favor + of standard nft sets. + + Reported-by: Slavko + Signed-off-by: Florian Westphal + Reviewed-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + doc/nft.txt | 15 +++------ + src/parser_bison.y | 79 ++++++++++++---------------------------------- + 2 files changed, 26 insertions(+), 68 deletions(-) + +diff --git a/doc/nft.txt b/doc/nft.txt +index 2080c07..77f2bc3 100644 +--- a/doc/nft.txt ++++ b/doc/nft.txt +@@ -585,7 +585,7 @@ section describes nft set syntax in more detail. + [verse] + *add set* ['family'] 'table' 'set' *{ type* 'type' | *typeof* 'expression' *;* [*flags* 'flags' *;*] [*timeout* 'timeout' *;*] [*gc-interval* 'gc-interval' *;*] [*elements = {* 'element'[*,* ...] *} ;*] [*size* 'size' *;*] [*comment* 'comment' *;*'] [*policy* 'policy' *;*] [*auto-merge ;*] *}* + {*delete* | *destroy* | *list* | *flush* | *reset* } *set* ['family'] 'table' 'set' +-*list sets* ['family'] ++*list sets* ['family'] ['table'] + *delete set* ['family'] 'table' *handle* 'handle' + {*add* | *delete* | *destroy* } *element* ['family'] 'table' 'set' *{* 'element'[*,* ...] *}* + +@@ -639,7 +639,7 @@ MAPS + [verse] + *add map* ['family'] 'table' 'map' *{ type* 'type' | *typeof* 'expression' [*flags* 'flags' *;*] [*elements = {* 'element'[*,* ...] *} ;*] [*size* 'size' *;*] [*comment* 'comment' *;*'] [*policy* 'policy' *;*] *}* + {*delete* | *destroy* | *list* | *flush* | *reset* } *map* ['family'] 'table' 'map' +-*list maps* ['family'] ++*list maps* ['family'] ['table'] + + Maps store data based on some specific key used as input. They are uniquely identified by a user-defined name and attached to tables. + +@@ -736,7 +736,7 @@ FLOWTABLES + ----------- + [verse] + {*add* | *create*} *flowtable* ['family'] 'table' 'flowtable' *{ hook* 'hook' *priority* 'priority' *; devices = {* 'device'[*,* ...] *} ; }* +-*list flowtables* ['family'] ++*list flowtables* ['family'] ['table'] + {*delete* | *destroy* | *list*} *flowtable* ['family'] 'table' 'flowtable' + *delete* *flowtable* ['family'] 'table' *handle* 'handle' + +@@ -787,13 +787,8 @@ STATEFUL OBJECTS + *destroy* 'counter' ['family'] 'table' *handle* 'handle' + *destroy* 'quota' ['family'] 'table' *handle* 'handle' + *destroy* 'limit' ['family'] 'table' *handle* 'handle' +-*list counters* ['family'] +-*list quotas* ['family'] +-*list limits* ['family'] +-*reset counters* ['family'] +-*reset quotas* ['family'] +-*reset counters* ['family'] 'table' +-*reset quotas* ['family'] 'table' ++*list* { *counters* | *limits* | *quotas* } ['family'] ['table'] ++*reset* { *counters* | *quotas* } ['family'] ['table'] + + Stateful objects are attached to tables and are identified by a unique name. + They group stateful information from rules, to reference them in rules the +diff --git a/src/parser_bison.y b/src/parser_bison.y +index 6435e8e..e601934 100644 +--- a/src/parser_bison.y ++++ b/src/parser_bison.y +@@ -725,6 +725,9 @@ int nft_lex(void *, void *, void *); + %type basehook_spec + %destructor { handle_free(&$$); } basehook_spec + ++%type list_cmd_spec_any list_cmd_spec_table ++%destructor { handle_free(&$$); } list_cmd_spec_any list_cmd_spec_table ++ + %type family_spec family_spec_explicit + %type int_num chain_policy + %type extended_prio_spec prio_spec +@@ -1568,6 +1571,13 @@ get_cmd : ELEMENT set_spec set_block_expr + } + ; + ++list_cmd_spec_table : TABLE table_spec { $$ = $2; } ++ | table_spec ++ ; ++list_cmd_spec_any : list_cmd_spec_table ++ | ruleset_spec ++ ; ++ + list_cmd : TABLE table_spec + { + $$ = cmd_alloc(CMD_LIST, CMD_OBJ_TABLE, &$2, &@$, NULL); +@@ -1584,74 +1594,50 @@ list_cmd : TABLE table_spec + { + $$ = cmd_alloc(CMD_LIST, CMD_OBJ_CHAINS, &$2, &@$, NULL); + } +- | SETS ruleset_spec ++ | SETS list_cmd_spec_any + { + $$ = cmd_alloc(CMD_LIST, CMD_OBJ_SETS, &$2, &@$, NULL); + } +- | SETS TABLE table_spec +- { +- $$ = cmd_alloc(CMD_LIST, CMD_OBJ_SETS, &$3, &@$, NULL); +- } + | SET set_spec + { + $$ = cmd_alloc(CMD_LIST, CMD_OBJ_SET, &$2, &@$, NULL); + } +- | COUNTERS ruleset_spec ++ | COUNTERS list_cmd_spec_any + { + $$ = cmd_alloc(CMD_LIST, CMD_OBJ_COUNTERS, &$2, &@$, NULL); + } +- | COUNTERS TABLE table_spec +- { +- $$ = cmd_alloc(CMD_LIST, CMD_OBJ_COUNTERS, &$3, &@$, NULL); +- } + | COUNTER obj_spec close_scope_counter + { + $$ = cmd_alloc(CMD_LIST, CMD_OBJ_COUNTER, &$2, &@$, NULL); + } +- | QUOTAS ruleset_spec ++ | QUOTAS list_cmd_spec_any + { + $$ = cmd_alloc(CMD_LIST, CMD_OBJ_QUOTAS, &$2, &@$, NULL); + } +- | QUOTAS TABLE table_spec +- { +- $$ = cmd_alloc(CMD_LIST, CMD_OBJ_QUOTAS, &$3, &@$, NULL); +- } + | QUOTA obj_spec close_scope_quota + { + $$ = cmd_alloc(CMD_LIST, CMD_OBJ_QUOTA, &$2, &@$, NULL); + } +- | LIMITS ruleset_spec ++ | LIMITS list_cmd_spec_any + { + $$ = cmd_alloc(CMD_LIST, CMD_OBJ_LIMITS, &$2, &@$, NULL); + } +- | LIMITS TABLE table_spec +- { +- $$ = cmd_alloc(CMD_LIST, CMD_OBJ_LIMITS, &$3, &@$, NULL); +- } + | LIMIT obj_spec close_scope_limit + { + $$ = cmd_alloc(CMD_LIST, CMD_OBJ_LIMIT, &$2, &@$, NULL); + } +- | SECMARKS ruleset_spec ++ | SECMARKS list_cmd_spec_any + { + $$ = cmd_alloc(CMD_LIST, CMD_OBJ_SECMARKS, &$2, &@$, NULL); + } +- | SECMARKS TABLE table_spec +- { +- $$ = cmd_alloc(CMD_LIST, CMD_OBJ_SECMARKS, &$3, &@$, NULL); +- } + | SECMARK obj_spec close_scope_secmark + { + $$ = cmd_alloc(CMD_LIST, CMD_OBJ_SECMARK, &$2, &@$, NULL); + } +- | SYNPROXYS ruleset_spec ++ | SYNPROXYS list_cmd_spec_any + { + $$ = cmd_alloc(CMD_LIST, CMD_OBJ_SYNPROXYS, &$2, &@$, NULL); + } +- | SYNPROXYS TABLE table_spec +- { +- $$ = cmd_alloc(CMD_LIST, CMD_OBJ_SYNPROXYS, &$3, &@$, NULL); +- } + | SYNPROXY obj_spec close_scope_synproxy + { + $$ = cmd_alloc(CMD_LIST, CMD_OBJ_SYNPROXY, &$2, &@$, NULL); +@@ -1676,7 +1662,7 @@ list_cmd : TABLE table_spec + { + $$ = cmd_alloc(CMD_LIST, CMD_OBJ_METER, &$2, &@$, NULL); + } +- | FLOWTABLES ruleset_spec ++ | FLOWTABLES list_cmd_spec_any + { + $$ = cmd_alloc(CMD_LIST, CMD_OBJ_FLOWTABLES, &$2, &@$, NULL); + } +@@ -1684,7 +1670,7 @@ list_cmd : TABLE table_spec + { + $$ = cmd_alloc(CMD_LIST, CMD_OBJ_FLOWTABLE, &$2, &@$, NULL); + } +- | MAPS ruleset_spec ++ | MAPS list_cmd_spec_any + { + $$ = cmd_alloc(CMD_LIST, CMD_OBJ_MAPS, &$2, &@$, NULL); + } +@@ -1726,36 +1712,18 @@ basehook_spec : ruleset_spec + } + ; + +-reset_cmd : COUNTERS ruleset_spec ++reset_cmd : COUNTERS list_cmd_spec_any + { + $$ = cmd_alloc(CMD_RESET, CMD_OBJ_COUNTERS, &$2, &@$, NULL); + } +- | COUNTERS table_spec +- { +- $$ = cmd_alloc(CMD_RESET, CMD_OBJ_COUNTERS, &$2, &@$, NULL); +- } +- | COUNTERS TABLE table_spec +- { +- /* alias of previous rule. */ +- $$ = cmd_alloc(CMD_RESET, CMD_OBJ_COUNTERS, &$3, &@$, NULL); +- } + | COUNTER obj_spec close_scope_counter + { + $$ = cmd_alloc(CMD_RESET, CMD_OBJ_COUNTER, &$2,&@$, NULL); + } +- | QUOTAS ruleset_spec ++ | QUOTAS list_cmd_spec_any + { + $$ = cmd_alloc(CMD_RESET, CMD_OBJ_QUOTAS, &$2, &@$, NULL); + } +- | QUOTAS TABLE table_spec +- { +- $$ = cmd_alloc(CMD_RESET, CMD_OBJ_QUOTAS, &$3, &@$, NULL); +- } +- | QUOTAS table_spec +- { +- /* alias of previous rule. */ +- $$ = cmd_alloc(CMD_RESET, CMD_OBJ_QUOTAS, &$2, &@$, NULL); +- } + | QUOTA obj_spec close_scope_quota + { + $$ = cmd_alloc(CMD_RESET, CMD_OBJ_QUOTA, &$2, &@$, NULL); +@@ -1764,15 +1732,10 @@ reset_cmd : COUNTERS ruleset_spec + { + $$ = cmd_alloc(CMD_RESET, CMD_OBJ_RULES, &$2, &@$, NULL); + } +- | RULES table_spec ++ | RULES list_cmd_spec_table + { + $$ = cmd_alloc(CMD_RESET, CMD_OBJ_TABLE, &$2, &@$, NULL); + } +- | RULES TABLE table_spec +- { +- /* alias of previous rule. */ +- $$ = cmd_alloc(CMD_RESET, CMD_OBJ_TABLE, &$3, &@$, NULL); +- } + | RULES chain_spec + { + $$ = cmd_alloc(CMD_RESET, CMD_OBJ_CHAIN, &$2, &@$, NULL); diff --git a/0155-evaluate-auto-merge-is-only-available-for-singleton-.patch b/0155-evaluate-auto-merge-is-only-available-for-singleton-.patch new file mode 100644 index 0000000..82e2666 --- /dev/null +++ b/0155-evaluate-auto-merge-is-only-available-for-singleton-.patch @@ -0,0 +1,42 @@ +From 6e340dc11b6d710909c1da9e48f1c942a17cdc8b Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:11:00 +0200 +Subject: [PATCH] evaluate: auto-merge is only available for singleton interval + sets + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 65382b888e266e2e3d49a418073fd76dcc4815a7 + +commit 65382b888e266e2e3d49a418073fd76dcc4815a7 +Author: Pablo Neira Ayuso +Date: Thu Feb 20 17:55:15 2025 +0100 + + evaluate: auto-merge is only available for singleton interval sets + + auto-merge is only available to interval sets with one value only, + untoggle this flag for concatenation with intervals. + + Later, this can be hardened to reject it. + + Fixes: 30f667920601 ("src: add 'auto-merge' option to sets") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/evaluate.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/src/evaluate.c b/src/evaluate.c +index 33f5724..2cc17c3 100644 +--- a/src/evaluate.c ++++ b/src/evaluate.c +@@ -4785,6 +4785,9 @@ static int set_evaluate(struct eval_ctx *ctx, struct set *set) + sizeof(set->desc.field_len)); + set->desc.field_count = set->key->field_count; + set->flags |= NFT_SET_CONCAT; ++ ++ if (set->automerge) ++ set->automerge = false; + } + + if (set_is_datamap(set->flags)) { diff --git a/0156-fib-Change-data-type-of-fib-oifname-to-ifname.patch b/0156-fib-Change-data-type-of-fib-oifname-to-ifname.patch new file mode 100644 index 0000000..662a4c1 --- /dev/null +++ b/0156-fib-Change-data-type-of-fib-oifname-to-ifname.patch @@ -0,0 +1,89 @@ +From 0e922765197331ee4b5c03f81fc8369b8e9ffd71 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:11:00 +0200 +Subject: [PATCH] fib: Change data type of fib oifname to "ifname" + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 316d99246644268e5e0453afa3ade163fda21d7f + +commit 316d99246644268e5e0453afa3ade163fda21d7f +Author: Xiao Liang +Date: Tue Feb 25 18:02:17 2025 +0800 + + fib: Change data type of fib oifname to "ifname" + + Change data type of fib oifname from "string" to "ifname", so that it + can be matched against a set of ifnames: + + set x { + type ifname + } + chain y { + fib saddr oifname @x drop + } + + Signed-off-by: Xiao Liang + Signed-off-by: Florian Westphal + +Signed-off-by: Phil Sutter +--- + doc/primary-expression.txt | 4 ++-- + src/fib.c | 2 +- + tests/shell/testcases/sets/0029named_ifname_dtype_0 | 1 + + tests/shell/testcases/sets/dumps/0029named_ifname_dtype_0.nft | 1 + + 4 files changed, 5 insertions(+), 3 deletions(-) + +diff --git a/doc/primary-expression.txt b/doc/primary-expression.txt +index e85fa58..43fa40c 100644 +--- a/doc/primary-expression.txt ++++ b/doc/primary-expression.txt +@@ -348,10 +348,10 @@ address types can be shown with *nft* *describe* *fib_addrtype*. + |Keyword| Description| Result Type + |oif| + Output interface index| +-integer (32 bit) ++iface_index + |oifname| + Output interface name| +-string ++ifname + |type| + Address type | + fib_addrtype (see *nft* *describe* *fib_addrtype* for a list) +diff --git a/src/fib.c b/src/fib.c +index e95271c..5a7c117 100644 +--- a/src/fib.c ++++ b/src/fib.c +@@ -179,7 +179,7 @@ struct expr *fib_expr_alloc(const struct location *loc, + type = &ifindex_type; + break; + case NFT_FIB_RESULT_OIFNAME: +- type = &string_type; ++ type = &ifname_type; + len = IFNAMSIZ * BITS_PER_BYTE; + break; + case NFT_FIB_RESULT_ADDRTYPE: +diff --git a/tests/shell/testcases/sets/0029named_ifname_dtype_0 b/tests/shell/testcases/sets/0029named_ifname_dtype_0 +index 2dbcd22..ea58140 100755 +--- a/tests/shell/testcases/sets/0029named_ifname_dtype_0 ++++ b/tests/shell/testcases/sets/0029named_ifname_dtype_0 +@@ -40,6 +40,7 @@ EXPECTED="table inet t { + chain c { + iifname @s accept + oifname @s accept ++ fib saddr oifname @s accept + tcp dport . meta iifname @sc accept + meta iifname . meta mark @nv accept + } +diff --git a/tests/shell/testcases/sets/dumps/0029named_ifname_dtype_0.nft b/tests/shell/testcases/sets/dumps/0029named_ifname_dtype_0.nft +index 55cd4f2..6f9832a 100644 +--- a/tests/shell/testcases/sets/dumps/0029named_ifname_dtype_0.nft ++++ b/tests/shell/testcases/sets/dumps/0029named_ifname_dtype_0.nft +@@ -51,6 +51,7 @@ table inet t { + chain c { + iifname @s accept + oifname @s accept ++ fib saddr oifname @s accept + tcp dport . iifname @sc accept + iifname . meta mark @nv accept + } diff --git a/0157-evaluate-optimize-zero-length-range.patch b/0157-evaluate-optimize-zero-length-range.patch new file mode 100644 index 0000000..89f3ed1 --- /dev/null +++ b/0157-evaluate-optimize-zero-length-range.patch @@ -0,0 +1,66 @@ +From 1a384d6d3f411695375ee5d04a32cc317a295b30 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:11:00 +0200 +Subject: [PATCH] evaluate: optimize zero length range + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit deda274293f80f9718de4cbb416bd2b2bf296709 + +commit deda274293f80f9718de4cbb416bd2b2bf296709 +Author: Pablo Neira Ayuso +Date: Sat Feb 22 00:32:11 2025 +0100 + + evaluate: optimize zero length range + + A rule like the following: + + ... tcp dport 22-22 ... + + results in a range expression to match from 22 to 22. + + Simplify to singleton value so a cmp is used instead. + + This optimization already exists in set elements which might explain + this overlook. + + Fixes: 7a6e16040d65 ("evaluate: allow for zero length ranges") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/evaluate.c | 12 +++++++++--- + 1 file changed, 9 insertions(+), 3 deletions(-) + +diff --git a/src/evaluate.c b/src/evaluate.c +index 2cc17c3..7059108 100644 +--- a/src/evaluate.c ++++ b/src/evaluate.c +@@ -1285,12 +1285,12 @@ static int __expr_evaluate_range(struct eval_ctx *ctx, struct expr **expr) + return 0; + } + +-static int expr_evaluate_range(struct eval_ctx *ctx, struct expr **expr) ++static int expr_evaluate_range(struct eval_ctx *ctx, struct expr **exprp) + { +- struct expr *range = *expr, *left, *right; ++ struct expr *range = *exprp, *left, *right; + int rc; + +- rc = __expr_evaluate_range(ctx, expr); ++ rc = __expr_evaluate_range(ctx, exprp); + if (rc) + return rc; + +@@ -1300,6 +1300,12 @@ static int expr_evaluate_range(struct eval_ctx *ctx, struct expr **expr) + if (mpz_cmp(left->value, right->value) > 0) + return expr_error(ctx->msgs, range, "Range negative size"); + ++ if (mpz_cmp(left->value, right->value) == 0) { ++ *exprp = expr_get(left); ++ expr_free(range); ++ return 0; ++ } ++ + datatype_set(range, left->dtype); + range->flags |= EXPR_F_CONSTANT; + return 0; diff --git a/0158-payload-return-early-if-dependency-is-not-a-payload-.patch b/0158-payload-return-early-if-dependency-is-not-a-payload-.patch new file mode 100644 index 0000000..3a22204 --- /dev/null +++ b/0158-payload-return-early-if-dependency-is-not-a-payload-.patch @@ -0,0 +1,47 @@ +From 8dfd461d9a2ca18d67bd52e3a8aa52febd73d265 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:11:00 +0200 +Subject: [PATCH] payload: return early if dependency is not a payload + expression + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 50f45c004adbab6a077609088becf62d2651101f + +commit 50f45c004adbab6a077609088becf62d2651101f +Author: Florian Westphal +Date: Tue Feb 25 21:13:33 2025 +0100 + + payload: return early if dependency is not a payload expression + + if (dep->left->payload.base != PROTO_BASE_TRANSPORT_HDR) + + is legal only after checking that ->left points to an + EXPR_PAYLOAD expression. The dependency store can also contain + EXPR_META, in this case we access a bogus part of the union. + + The payload_may_dependency_kill_icmp helper can't handle a META + dep either, so return early. + + Fixes: 533565244d88 ("payload: check icmp dependency before removing previous icmp expression") + Signed-off-by: Florian Westphal + Reviewed-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/payload.c | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/src/payload.c b/src/payload.c +index 5de3d32..25c3660 100644 +--- a/src/payload.c ++++ b/src/payload.c +@@ -895,7 +895,8 @@ static bool payload_may_dependency_kill(struct payload_dep_ctx *ctx, + if (expr->payload.base != PROTO_BASE_TRANSPORT_HDR) + return true; + +- if (dep->left->payload.base != PROTO_BASE_TRANSPORT_HDR) ++ if (dep->left->etype != EXPR_PAYLOAD || ++ dep->left->payload.base != PROTO_BASE_TRANSPORT_HDR) + return true; + + if (dep->left->payload.desc == &proto_icmp) diff --git a/0159-payload-honor-inner-payload-description-in-payload_e.patch b/0159-payload-honor-inner-payload-description-in-payload_e.patch new file mode 100644 index 0000000..a161ac5 --- /dev/null +++ b/0159-payload-honor-inner-payload-description-in-payload_e.patch @@ -0,0 +1,43 @@ +From b2e4263f5ada09a10011aa3f5286ecde3e6c064b Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:11:01 +0200 +Subject: [PATCH] payload: honor inner payload description in + payload_expr_cmp() + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 095a084dc0adf7079409e9ff65a31cabf937e615 + +commit 095a084dc0adf7079409e9ff65a31cabf937e615 +Author: Pablo Neira Ayuso +Date: Wed Feb 26 00:39:01 2025 +0100 + + payload: honor inner payload description in payload_expr_cmp() + + payload comparison must consider inner_desc. + + No test update because I could not find any specific bug related to + this. I found it through source code inspection. + + Fixes: 772892a018b4 ("src: add vxlan matching support") + Signed-off-by: Pablo Neira Ayuso + Signed-off-by: Florian Westphal + +Signed-off-by: Phil Sutter +--- + src/payload.c | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/src/payload.c b/src/payload.c +index 25c3660..f31a812 100644 +--- a/src/payload.c ++++ b/src/payload.c +@@ -62,7 +62,8 @@ static void payload_expr_print(const struct expr *expr, struct output_ctx *octx) + + bool payload_expr_cmp(const struct expr *e1, const struct expr *e2) + { +- return e1->payload.desc == e2->payload.desc && ++ return e1->payload.inner_desc == e2->payload.inner_desc && ++ e1->payload.desc == e2->payload.desc && + e1->payload.tmpl == e2->payload.tmpl && + e1->payload.base == e2->payload.base && + e1->payload.offset == e2->payload.offset; diff --git a/0160-segtree-fix-string-data-initialisation.patch b/0160-segtree-fix-string-data-initialisation.patch new file mode 100644 index 0000000..8d998e4 --- /dev/null +++ b/0160-segtree-fix-string-data-initialisation.patch @@ -0,0 +1,136 @@ +From 6b999820a60ddaecb544e5c5266307a662879afc Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:11:01 +0200 +Subject: [PATCH] segtree: fix string data initialisation + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 63e3d5953c144abbc4ead2665ad7cec799c4cb64 + +commit 63e3d5953c144abbc4ead2665ad7cec799c4cb64 +Author: Florian Westphal +Date: Wed Mar 5 16:01:48 2025 +0100 + + segtree: fix string data initialisation + + This uses the wrong length. This must re-use the length of the datatype, + not the string length. + + The added test cases will fail without the fix due to erroneous + overlap detection, which in itself is due to incorrect sorting of + the elements. + + Example error: + netlink: Error: interval overlaps with an existing one + add element inet testifsets simple_wild { "2-1" } failed. + table inet testifsets { + ... elements = { "1-1", "abcdef*", "othername", "ppp0" } + + ... but clearly "2-1" doesn't overlap with any existing members. + The false detection is because of the "acvdef*" wildcard getting sorted + at the beginning of the list which is because its erronously initialised + as a 64bit number instead of 128 bits (16 bytes / IFNAMSIZ). + + Fixes: 5e393ea1fc0a ("segtree: add string "range" reversal support") + Signed-off-by: Florian Westphal + Reviewed-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/segtree.c | 2 +- + tests/shell/testcases/sets/sets_with_ifnames | 62 ++++++++++++++++++++ + 2 files changed, 63 insertions(+), 1 deletion(-) + +diff --git a/src/segtree.c b/src/segtree.c +index c5f27c9..a57d865 100644 +--- a/src/segtree.c ++++ b/src/segtree.c +@@ -495,7 +495,7 @@ static struct expr *interval_to_string(struct expr *low, struct expr *i, const m + + expr = constant_expr_alloc(&low->location, low->dtype, + BYTEORDER_HOST_ENDIAN, +- (str_len + 1) * BITS_PER_BYTE, data); ++ len * BITS_PER_BYTE, data); + + return __expr_to_set_elem(low, expr); + } +diff --git a/tests/shell/testcases/sets/sets_with_ifnames b/tests/shell/testcases/sets/sets_with_ifnames +index 9531c85..713778b 100755 +--- a/tests/shell/testcases/sets/sets_with_ifnames ++++ b/tests/shell/testcases/sets/sets_with_ifnames +@@ -103,10 +103,67 @@ check_matching_icmp_ppp() + fi + } + ++check_add_del_ifnames() ++{ ++ local what="$1" ++ local setname="$2" ++ local prefix="$3" ++ local data="$4" ++ local i=0 ++ ++ for i in $(seq 1 5);do ++ local cmd="element inet testifsets $setname { " ++ local to_batch=16 ++ ++ for j in $(seq 1 $to_batch);do ++ local name=$(printf '"%x-%d"' $i $j) ++ ++ [ -n "$prefix" ] && cmd="$cmd $prefix . " ++ ++ cmd="$cmd $name" ++ ++ [ -n "$data" ] && cmd="$cmd : $data" ++ ++ if [ $j -lt $to_batch ] ; then ++ cmd="$cmd, " ++ fi ++ done ++ ++ cmd="$cmd }" ++ ++ if ! $NFT "$what" "$cmd"; then ++ echo "$what $cmd failed." ++ $NFT list set inet testifsets $setname ++ exit 1 ++ fi ++ ++ if ! ip netns exec "$ns1" $NFT "$what" "$cmd"; then ++ echo "$ns1 $what $cmd failed." ++ ip netns exec "$ns1" $NFT list set inet testifsets $setname ++ exit 1 ++ fi ++ done ++} ++ ++check_add_ifnames() ++{ ++ check_add_del_ifnames "add" "$1" "$2" "$3" ++} ++ ++check_del_ifnames() ++{ ++ check_add_del_ifnames "delete" "$1" "$2" "$3" ++} ++ + ip netns add "$ns1" || exit 111 + ip netns add "$ns2" || exit 111 + ip netns exec "$ns1" $NFT -f "$dumpfile" || exit 3 + ++check_add_ifnames "simple" "" "" ++check_add_ifnames "simple_wild" "" "" ++check_add_ifnames "concat" "10.1.2.2" "" ++check_add_ifnames "map_wild" "" "drop" ++ + for n in abcdef0 abcdef1 othername;do + check_elem simple $n + done +@@ -148,3 +205,8 @@ ip -net "$ns2" addr add 10.1.2.2/24 dev veth0 + ip -net "$ns2" addr add 10.2.2.2/24 dev veth1 + + check_matching_icmp_ppp ++ ++check_del_ifnames "simple" "" "" ++check_del_ifnames "simple_wild" "" "" ++check_del_ifnames "concat" "10.1.2.2" "" ++check_del_ifnames "map_wild" "" "drop" diff --git a/0161-evaluate-release-existing-datatype-when-evaluating-u.patch b/0161-evaluate-release-existing-datatype-when-evaluating-u.patch new file mode 100644 index 0000000..ac54689 --- /dev/null +++ b/0161-evaluate-release-existing-datatype-when-evaluating-u.patch @@ -0,0 +1,52 @@ +From cab75933e5ef60083de962c17248c820bfa1bec3 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:11:01 +0200 +Subject: [PATCH] evaluate: release existing datatype when evaluating unary + expression + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 494a6ed120065b764f07acd05789b816625e8e13 + +commit 494a6ed120065b764f07acd05789b816625e8e13 +Author: Pablo Neira Ayuso +Date: Fri Feb 28 15:54:55 2025 +0100 + + evaluate: release existing datatype when evaluating unary expression + + Use __datatype_set() to release the existing datatype before assigning + the new one, otherwise ASAN reports the following memleak: + + Direct leak of 104 byte(s) in 1 object(s) allocated from: + #0 0x7fbc8a2b89cf in __interceptor_malloc ../../../../src/libsa + #1 0x7fbc898c96c2 in xmalloc src/utils.c:31 + #2 0x7fbc8971a182 in datatype_clone src/datatype.c:1406 + #3 0x7fbc89737c35 in expr_evaluate_unary src/evaluate.c:1366 + #4 0x7fbc89758ae9 in expr_evaluate src/evaluate.c:3057 + #5 0x7fbc89726bd9 in byteorder_conversion src/evaluate.c:243 + #6 0x7fbc89739ff0 in expr_evaluate_bitwise src/evaluate.c:1491 + #7 0x7fbc8973b4f8 in expr_evaluate_binop src/evaluate.c:1600 + #8 0x7fbc89758b01 in expr_evaluate src/evaluate.c:3059 + #9 0x7fbc8975ae0e in stmt_evaluate_arg src/evaluate.c:3198 + #10 0x7fbc8975c51d in stmt_evaluate_payload src/evaluate.c:330 + + Fixes: faa6908fad60 ("evaluate: clone unary expression datatype to deal with dynamic datatype") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/evaluate.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/evaluate.c b/src/evaluate.c +index 7059108..a294bbd 100644 +--- a/src/evaluate.c ++++ b/src/evaluate.c +@@ -1339,7 +1339,7 @@ static int expr_evaluate_unary(struct eval_ctx *ctx, struct expr **expr) + BUG("invalid unary operation %u\n", unary->op); + } + +- unary->dtype = datatype_clone(arg->dtype); ++ __datatype_set(unary, datatype_clone(arg->dtype)); + unary->byteorder = byteorder; + unary->len = arg->len; + return 0; diff --git a/0162-segtree-incomplete-output-in-get-element-command-wit.patch b/0162-segtree-incomplete-output-in-get-element-command-wit.patch new file mode 100644 index 0000000..f5e0484 --- /dev/null +++ b/0162-segtree-incomplete-output-in-get-element-command-wit.patch @@ -0,0 +1,140 @@ +From 95248181bf7d82073184ce718c7b6b8ce461b7d3 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:11:01 +0200 +Subject: [PATCH] segtree: incomplete output in get element command with maps + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 6db28b2d71e7f61c64338787be5d82edfdb62a21 + +commit 6db28b2d71e7f61c64338787be5d82edfdb62a21 +Author: Pablo Neira Ayuso +Date: Thu Mar 6 18:49:21 2025 +0100 + + segtree: incomplete output in get element command with maps + + get element command displays an incomplete range. + + Using this simple test ruleset: + + table ip x { + map y { + typeof ip saddr : meta mark + counter + flags interval,timeout + elements = { 1.1.1.1-1.1.1.10 timeout 10m : 20, 2.2.2.2-2.2.2.5 timeout 10m : 30} + } + + then, invoking the get element command: + + # nft get element x y { 1.1.1.2 } + + results in, before (incomplete output): + + table ip x { + map y { + type ipv4_addr : mark + flags interval,timeout + elements = { 1.1.1.1 counter packets 0 bytes 0 timeout 10m expires 1m24s160ms : 0x00000014 } + } + } + + Note that it displays 1.1.1.1, instead of 1.1.1.1-1.1.1.10. + + After this fix: + + table ip x { + map y { + type ipv4_addr : mark + flags interval,timeout + elements = { 1.1.1.1-1.1.1.10 counter packets 0 bytes 0 timeout 10m expires 1m24s160ms : 0x00000014 } + } + } + + Fixes: a43cc8d53096 ("src: support for get element command") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/segtree.c | 39 ++++++++++++++++++++------------------- + 1 file changed, 20 insertions(+), 19 deletions(-) + +diff --git a/src/segtree.c b/src/segtree.c +index a57d865..ee68ce1 100644 +--- a/src/segtree.c ++++ b/src/segtree.c +@@ -110,19 +110,34 @@ struct expr *get_set_intervals(const struct set *set, const struct expr *init) + return new_init; + } + ++static struct expr *expr_value(struct expr *expr) ++{ ++ switch (expr->etype) { ++ case EXPR_MAPPING: ++ return expr->left->key; ++ case EXPR_SET_ELEM: ++ return expr->key; ++ case EXPR_VALUE: ++ return expr; ++ default: ++ BUG("invalid expression type %s\n", expr_name(expr)); ++ } ++} ++ + static struct expr *get_set_interval_find(const struct set *cache_set, + struct expr *left, + struct expr *right) + { + const struct set *set = cache_set; + struct expr *range = NULL; +- struct expr *i; ++ struct expr *i, *key; + mpz_t val; + + mpz_init2(val, set->key->len); + + list_for_each_entry(i, &set->init->expressions, list) { +- switch (i->key->etype) { ++ key = expr_value(i); ++ switch (key->etype) { + case EXPR_VALUE: + if (expr_basetype(i->key)->type != TYPE_STRING) + break; +@@ -131,14 +146,14 @@ static struct expr *get_set_interval_find(const struct set *cache_set, + case EXPR_PREFIX: + case EXPR_RANGE: + range_expr_value_low(val, i); +- if (left && mpz_cmp(left->key->value, val)) ++ if (left && mpz_cmp(expr_value(left)->value, val)) + break; + + range_expr_value_high(val, i); +- if (right && mpz_cmp(right->key->value, val)) ++ if (right && mpz_cmp(expr_value(right)->value, val)) + break; + +- range = expr_clone(i->key); ++ range = expr_clone(i); + goto out; + default: + break; +@@ -150,20 +165,6 @@ out: + return range; + } + +-static struct expr *expr_value(struct expr *expr) +-{ +- switch (expr->etype) { +- case EXPR_MAPPING: +- return expr->left->key; +- case EXPR_SET_ELEM: +- return expr->key; +- case EXPR_VALUE: +- return expr; +- default: +- BUG("invalid expression type %s\n", expr_name(expr)); +- } +-} +- + static struct expr *__expr_to_set_elem(struct expr *low, struct expr *expr) + { + struct expr *elem = set_elem_expr_alloc(&low->location, expr); diff --git a/0163-src-do-not-merge-a-set-with-a-erroneous-one.patch b/0163-src-do-not-merge-a-set-with-a-erroneous-one.patch new file mode 100644 index 0000000..42dd736 --- /dev/null +++ b/0163-src-do-not-merge-a-set-with-a-erroneous-one.patch @@ -0,0 +1,124 @@ +From 5ac397f2bf496a0e8ee34cf737be22065ffb92e1 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:11:01 +0200 +Subject: [PATCH] src: do not merge a set with a erroneous one + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit ea011231c06cbe828cf6056bc9c3d116e1f528d5 + +commit ea011231c06cbe828cf6056bc9c3d116e1f528d5 +Author: Florian Westphal +Date: Fri Jan 12 13:19:26 2024 +0100 + + src: do not merge a set with a erroneous one + + The included sample causes a crash because we attempt to + range-merge a prefix expression with a symbolic expression. + + The first set is evaluated, the symbol expression evaluation fails + and nft queues an error message ("Could not resolve hostname"). + + However, nft continues evaluation. + + nft then encounters the same set definition again and merges the + new content with the preceeding one. + + But the first set structure is dodgy, it still contains the + unresolved symbolic expression. + + That then makes nft crash (assert) in the set internals. + + There are various different incarnations of this issue, but the low + level set processing code does not allow for any partially transformed + expressions to still remain. + + Before: + nft --check -f tests/shell/testcases/bogons/nft-f/invalid_range_expr_type_binop + BUG: invalid range expression type binop + nft: src/expression.c:1479: range_expr_value_low: Assertion `0' failed. + + After: + nft --check -f tests/shell/testcases/bogons/nft-f/invalid_range_expr_type_binop + invalid_range_expr_type_binop:4:18-25: Error: Could not resolve hostname: Name or service not known + elements = { 1&.141.0.1 - 192.168.0.2} + ^^^^^^^^ + + Signed-off-by: Florian Westphal + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + include/rule.h | 2 ++ + src/evaluate.c | 4 +++- + src/intervals.c | 2 +- + .../bogons/nft-f/invalid_range_expr_type_binop | 12 ++++++++++++ + 4 files changed, 18 insertions(+), 2 deletions(-) + create mode 100644 tests/shell/testcases/bogons/nft-f/invalid_range_expr_type_binop + +diff --git a/include/rule.h b/include/rule.h +index a82c794..fe281cc 100644 +--- a/include/rule.h ++++ b/include/rule.h +@@ -331,6 +331,7 @@ void rule_stmt_insert_at(struct rule *rule, struct stmt *nstmt, + * @policy: set mechanism policy + * @automerge: merge adjacents and overlapping elements, if possible + * @comment: comment ++ * @errors: expr evaluation errors seen + * @desc.size: count of set elements + * @desc.field_len: length of single concatenated fields, bytes + * @desc.field_count: count of concatenated fields +@@ -355,6 +356,7 @@ struct set { + bool root; + bool automerge; + bool key_typeof_valid; ++ bool errors; + const char *comment; + struct { + uint32_t size; +diff --git a/src/evaluate.c b/src/evaluate.c +index a294bbd..256ca10 100644 +--- a/src/evaluate.c ++++ b/src/evaluate.c +@@ -4718,8 +4718,10 @@ static int elems_evaluate(struct eval_ctx *ctx, struct set *set) + + __expr_set_context(&ctx->ectx, set->key->dtype, + set->key->byteorder, set->key->len, 0); +- if (expr_evaluate(ctx, &set->init) < 0) ++ if (expr_evaluate(ctx, &set->init) < 0) { ++ set->errors = true; + return -1; ++ } + if (set->init->etype != EXPR_SET) + return expr_error(ctx->msgs, set->init, "Set %s: Unexpected initial type %s, missing { }?", + set->handle.set.name, expr_name(set->init)); +diff --git a/src/intervals.c b/src/intervals.c +index 0618b49..31adb82 100644 +--- a/src/intervals.c ++++ b/src/intervals.c +@@ -136,7 +136,7 @@ static void set_sort_splice(struct expr *init, struct set *set) + set_to_range(init); + list_expr_sort(&init->expressions); + +- if (!existing_set) ++ if (!existing_set || existing_set->errors) + return; + + if (existing_set->init) { +diff --git a/tests/shell/testcases/bogons/nft-f/invalid_range_expr_type_binop b/tests/shell/testcases/bogons/nft-f/invalid_range_expr_type_binop +new file mode 100644 +index 0000000..514d6ff +--- /dev/null ++++ b/tests/shell/testcases/bogons/nft-f/invalid_range_expr_type_binop +@@ -0,0 +1,12 @@ ++table ip x { ++ map z { ++ type ipv4_addr : ipv4_addr ++ elements = { 1&.141.0.1 - 192.168.0.2} ++ } ++ ++ map z { ++ type ipv4_addr : ipv4_addr ++ flags interval ++ elements = { 10.141.0.0, * : 192.168.0.4 } ++ } ++} diff --git a/0164-tests-shell-detach-synproxy-test.patch b/0164-tests-shell-detach-synproxy-test.patch new file mode 100644 index 0000000..34d1370 --- /dev/null +++ b/0164-tests-shell-detach-synproxy-test.patch @@ -0,0 +1,167 @@ +From edefa780341f25a8116de9952ebdd33849462cbb Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:11:01 +0200 +Subject: [PATCH] tests: shell: detach synproxy test + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 72753990520d51c18f91db0412c05ca46a92e45e + +commit 72753990520d51c18f91db0412c05ca46a92e45e +Author: Pablo Neira Ayuso +Date: Tue Nov 21 20:59:06 2023 +0100 + + tests: shell: detach synproxy test + + Old kernels do not support synproxy, split existing tests with stateful objects. + + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + .../shell/testcases/sets/0024named_objects_0 | 15 ---------- + tests/shell/testcases/sets/0024synproxy_0 | 29 +++++++++++++++++++ + .../sets/dumps/0024named_objects_0.nft | 18 ------------ + .../testcases/sets/dumps/0024synproxy_0.nft | 23 +++++++++++++++ + 4 files changed, 52 insertions(+), 33 deletions(-) + create mode 100755 tests/shell/testcases/sets/0024synproxy_0 + create mode 100644 tests/shell/testcases/sets/dumps/0024synproxy_0.nft + +diff --git a/tests/shell/testcases/sets/0024named_objects_0 b/tests/shell/testcases/sets/0024named_objects_0 +index 6d21e38..21200c3 100755 +--- a/tests/shell/testcases/sets/0024named_objects_0 ++++ b/tests/shell/testcases/sets/0024named_objects_0 +@@ -18,15 +18,6 @@ table inet x { + quota user124 { + over 2000 bytes + } +- synproxy https-synproxy { +- mss 1460 +- wscale 7 +- timestamp sack-perm +- } +- synproxy other-synproxy { +- mss 1460 +- wscale 5 +- } + set y { + type ipv4_addr + } +@@ -34,15 +25,9 @@ table inet x { + type ipv4_addr : quota + elements = { 192.168.2.2 : "user124", 192.168.2.3 : "user124"} + } +- map test2 { +- type ipv4_addr : synproxy +- flags interval +- elements = { 192.168.1.0/24 : "https-synproxy", 192.168.2.0/24 : "other-synproxy" } +- } + chain y { + type filter hook input priority 0; policy accept; + counter name ip saddr map { 192.168.2.2 : "user123", 1.1.1.1 : "user123", 2.2.2.2 : "user123"} +- synproxy name ip saddr map { 192.168.1.0/24 : "https-synproxy", 192.168.2.0/24 : "other-synproxy" } + quota name ip saddr map @test drop + } + }" +diff --git a/tests/shell/testcases/sets/0024synproxy_0 b/tests/shell/testcases/sets/0024synproxy_0 +new file mode 100755 +index 0000000..ccaed03 +--- /dev/null ++++ b/tests/shell/testcases/sets/0024synproxy_0 +@@ -0,0 +1,29 @@ ++#!/bin/bash ++ ++# * creating valid named objects ++# * referencing them from a valid rule ++ ++RULESET=" ++table inet x { ++ synproxy https-synproxy { ++ mss 1460 ++ wscale 7 ++ timestamp sack-perm ++ } ++ synproxy other-synproxy { ++ mss 1460 ++ wscale 5 ++ } ++ map test2 { ++ type ipv4_addr : synproxy ++ flags interval ++ elements = { 192.168.1.0/24 : "https-synproxy", 192.168.2.0/24 : "other-synproxy" } ++ } ++ chain y { ++ type filter hook input priority 0; policy accept; ++ synproxy name ip saddr map { 192.168.1.0/24 : "https-synproxy", 192.168.2.0/24 : "other-synproxy" } ++ } ++}" ++ ++set -e ++$NFT -f - <<< "$RULESET" +diff --git a/tests/shell/testcases/sets/dumps/0024named_objects_0.nft b/tests/shell/testcases/sets/dumps/0024named_objects_0.nft +index 52d1bf6..2ffa4f2 100644 +--- a/tests/shell/testcases/sets/dumps/0024named_objects_0.nft ++++ b/tests/shell/testcases/sets/dumps/0024named_objects_0.nft +@@ -15,17 +15,6 @@ table inet x { + over 2000 bytes + } + +- synproxy https-synproxy { +- mss 1460 +- wscale 7 +- timestamp sack-perm +- } +- +- synproxy other-synproxy { +- mss 1460 +- wscale 5 +- } +- + set y { + type ipv4_addr + } +@@ -35,16 +24,9 @@ table inet x { + elements = { 192.168.2.2 : "user124", 192.168.2.3 : "user124" } + } + +- map test2 { +- type ipv4_addr : synproxy +- flags interval +- elements = { 192.168.1.0/24 : "https-synproxy", 192.168.2.0/24 : "other-synproxy" } +- } +- + chain y { + type filter hook input priority filter; policy accept; + counter name ip saddr map { 1.1.1.1 : "user123", 2.2.2.2 : "user123", 192.168.2.2 : "user123" } +- synproxy name ip saddr map { 192.168.1.0/24 : "https-synproxy", 192.168.2.0/24 : "other-synproxy" } + quota name ip saddr map @test drop + } + } +diff --git a/tests/shell/testcases/sets/dumps/0024synproxy_0.nft b/tests/shell/testcases/sets/dumps/0024synproxy_0.nft +new file mode 100644 +index 0000000..e0ee86d +--- /dev/null ++++ b/tests/shell/testcases/sets/dumps/0024synproxy_0.nft +@@ -0,0 +1,23 @@ ++table inet x { ++ synproxy https-synproxy { ++ mss 1460 ++ wscale 7 ++ timestamp sack-perm ++ } ++ ++ synproxy other-synproxy { ++ mss 1460 ++ wscale 5 ++ } ++ ++ map test2 { ++ type ipv4_addr : synproxy ++ flags interval ++ elements = { 192.168.1.0/24 : "https-synproxy", 192.168.2.0/24 : "other-synproxy" } ++ } ++ ++ chain y { ++ type filter hook input priority filter; policy accept; ++ synproxy name ip saddr map { 192.168.1.0/24 : "https-synproxy", 192.168.2.0/24 : "other-synproxy" } ++ } ++} diff --git a/0165-src-print-set-element-with-multi-word-description-in.patch b/0165-src-print-set-element-with-multi-word-description-in.patch new file mode 100644 index 0000000..76761cf --- /dev/null +++ b/0165-src-print-set-element-with-multi-word-description-in.patch @@ -0,0 +1,1984 @@ +From ff928ed92b955dfeff43288c71ff095649ab93c2 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:11:01 +0200 +Subject: [PATCH] src: print set element with multi-word description in single + one line + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit f133180ee0c72f4a0de4a7acad8a9d8476914bd4 + +commit f133180ee0c72f4a0de4a7acad8a9d8476914bd4 +Author: Pablo Neira Ayuso +Date: Thu Mar 13 22:28:27 2025 +0100 + + src: print set element with multi-word description in single one line + + If the set element: + + - represents a mapping + - has a timeout + - has a comment + - has counter/quota/limit + - concatenation (already printed in a single line before this patch) + + ie. if the set element requires several words, then print it in one + single line. + + Before this patch: + + table ip x { + set y { + typeof ip saddr + counter + elements = { 192.168.10.35 counter packets 0 bytes 0, 192.168.10.101 counter packets 0 bytes 0, + 192.168.10.135 counter packets 0 bytes 0 } + } + } + + After this patch: + + table ip x { + set y { + typeof ip saddr + counter + elements = { 192.168.10.35 counter packets 0 bytes 0, + 192.168.10.101 counter packets 0 bytes 0, + 192.168.10.135 counter packets 0 bytes 0 } + } + } + + Acked-by: Florian Westphal + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + include/nftables.h | 1 + + include/rule.h | 2 + + src/expression.c | 8 +- + src/netlink.c | 5 +- + src/rule.c | 9 + + .../maps/0003map_add_many_elements_0 | 9 +- + .../maps/0004interval_map_create_once_0 | 9 +- + .../testcases/maps/0008interval_map_delete_0 | 3 +- + .../dumps/0003map_add_many_elements_0.nft | 1440 +++++++++++------ + .../0005interval_map_add_many_elements_0.nft | 6 +- + .../maps/dumps/0006interval_map_overlap_0.nft | 3 +- + .../maps/dumps/0008interval_map_delete_0.nft | 3 +- + .../shell/testcases/maps/dumps/0011vmap_0.nft | 3 +- + .../maps/dumps/0017_map_variable_0.nft | 6 +- + .../testcases/maps/dumps/typeof_maps_0.nft | 3 +- + .../maps/dumps/vmap_mark_bitwise_0.nft | 3 +- + tests/shell/testcases/maps/typeof_maps_0 | 3 +- + .../sets/dumps/0024named_objects_0.nft | 3 +- + .../testcases/sets/dumps/0024synproxy_0.nft | 3 +- + .../shell/testcases/sets/dumps/0047nat_0.nft | 3 +- + .../sets/dumps/0048set_counters_0.nft | 3 +- + .../sets/dumps/0060set_multistmt_0.nft | 3 +- + .../sets/dumps/0060set_multistmt_1.nft | 3 +- + .../sets/dumps/0063set_catchall_0.nft | 6 +- + .../sets/dumps/0064map_catchall_0.nft | 6 +- + .../sets/dumps/0067nat_interval_0.nft | 3 +- + .../transactions/dumps/0047set_0.nft | 15 +- + 27 files changed, 1038 insertions(+), 526 deletions(-) + +diff --git a/include/nftables.h b/include/nftables.h +index 4b7c335..d6f4ea7 100644 +--- a/include/nftables.h ++++ b/include/nftables.h +@@ -38,6 +38,7 @@ static inline bool nft_input_json(const struct input_ctx *ictx) + + struct output_ctx { + unsigned int flags; ++ bool force_newline; + union { + FILE *output_fp; + struct cookie output_cookie; +diff --git a/include/rule.h b/include/rule.h +index fe281cc..7f0c1d0 100644 +--- a/include/rule.h ++++ b/include/rule.h +@@ -332,6 +332,7 @@ void rule_stmt_insert_at(struct rule *rule, struct stmt *nstmt, + * @automerge: merge adjacents and overlapping elements, if possible + * @comment: comment + * @errors: expr evaluation errors seen ++ * @elem_has_comment: element with comment seen (for printing) + * @desc.size: count of set elements + * @desc.field_len: length of single concatenated fields, bytes + * @desc.field_count: count of concatenated fields +@@ -357,6 +358,7 @@ struct set { + bool automerge; + bool key_typeof_valid; + bool errors; ++ bool elem_has_comment; + const char *comment; + struct { + uint32_t size; +diff --git a/src/expression.c b/src/expression.c +index adfdf27..09449da 100644 +--- a/src/expression.c ++++ b/src/expression.c +@@ -1128,11 +1128,15 @@ struct expr *list_expr_alloc(const struct location *loc) + return compound_expr_alloc(loc, EXPR_LIST); + } + +-static const char *calculate_delim(const struct expr *expr, int *count) ++static const char *calculate_delim(const struct expr *expr, int *count, ++ struct output_ctx *octx) + { + const char *newline = ",\n\t\t\t "; + const char *singleline = ", "; + ++ if (octx->force_newline) ++ return newline; ++ + if (set_is_anonymous(expr->set_flags)) + return singleline; + +@@ -1185,7 +1189,7 @@ static void set_expr_print(const struct expr *expr, struct output_ctx *octx) + nft_print(octx, "%s", d); + expr_print(i, octx); + count++; +- d = calculate_delim(expr, &count); ++ d = calculate_delim(expr, &count, octx); + } + + nft_print(octx, " }"); +diff --git a/src/netlink.c b/src/netlink.c +index 0caeeff..726003f 100644 +--- a/src/netlink.c ++++ b/src/netlink.c +@@ -1407,8 +1407,11 @@ key_end: + expr->timeout = nftnl_set_elem_get_u64(nlse, NFTNL_SET_ELEM_TIMEOUT); + if (nftnl_set_elem_is_set(nlse, NFTNL_SET_ELEM_EXPIRATION)) + expr->expiration = nftnl_set_elem_get_u64(nlse, NFTNL_SET_ELEM_EXPIRATION); +- if (nftnl_set_elem_is_set(nlse, NFTNL_SET_ELEM_USERDATA)) ++ if (nftnl_set_elem_is_set(nlse, NFTNL_SET_ELEM_USERDATA)) { + set_elem_parse_udata(nlse, expr); ++ if (expr->comment) ++ set->elem_has_comment = true; ++ } + if (nftnl_set_elem_is_set(nlse, NFTNL_SET_ELEM_EXPR)) { + const struct nftnl_expr *nle; + struct stmt *stmt; +diff --git a/src/rule.c b/src/rule.c +index 5d96a15..6516454 100644 +--- a/src/rule.c ++++ b/src/rule.c +@@ -420,7 +420,16 @@ static void do_set_print(const struct set *set, struct print_fmt_options *opts, + + if (set->init != NULL && set->init->size > 0) { + nft_print(octx, "%s%selements = ", opts->tab, opts->tab); ++ ++ if (set->timeout || set->elem_has_comment || ++ (set->flags & (NFT_SET_MAP | NFT_SET_OBJECT | ++ NFT_SET_TIMEOUT | NFT_SET_CONCAT)) || ++ !list_empty(&set->stmt_list)) ++ octx->force_newline = true; ++ + expr_print(set->init, octx); ++ octx->force_newline = false; ++ + nft_print(octx, "%s", opts->nl); + } + nft_print(octx, "%s}%s", opts->tab, opts->nl); +diff --git a/tests/shell/testcases/maps/0003map_add_many_elements_0 b/tests/shell/testcases/maps/0003map_add_many_elements_0 +index 2b254c5..427d94d 100755 +--- a/tests/shell/testcases/maps/0003map_add_many_elements_0 ++++ b/tests/shell/testcases/maps/0003map_add_many_elements_0 +@@ -24,19 +24,12 @@ generate_add() { + } + + generate_test() { +- count=0 + elements="" + for ((i=1; i<=HOWMANY; i++)) ; do + for ((j=1; j<=HOWMANY; j++)) ; do +- ((count++)) + elements="${elements}10.0.${i}.${j} : 10.0.${i}.${j}" + [ "$i" == "$HOWMANY" ] && [ "$j" == "$HOWMANY" ] && break +- if [ "$count" == "2" ] ; then +- count=0 +- elements="${elements},\\n\\t\\t\\t " +- else +- elements="${elements}, " +- fi ++ elements="${elements},\\n\\t\\t\\t " + done + done + echo -e "$elements" +diff --git a/tests/shell/testcases/maps/0004interval_map_create_once_0 b/tests/shell/testcases/maps/0004interval_map_create_once_0 +index 64f434a..7d38255 100755 +--- a/tests/shell/testcases/maps/0004interval_map_create_once_0 ++++ b/tests/shell/testcases/maps/0004interval_map_create_once_0 +@@ -30,19 +30,12 @@ generate_add() { + } + + generate_test() { +- count=0 + elements="" + for ((i=1; i<=HOWMANY; i++)) ; do + for ((j=1; j<=HOWMANY; j++)) ; do +- ((count++)) + elements="${elements}10.${i}.${j}.0/24 : 10.0.${i}.${j}" + [ "$i" == "$HOWMANY" ] && [ "$j" == "$HOWMANY" ] && break +- if [ "$count" == "2" ] ; then +- count=0 +- elements="${elements},\\n\\t\\t\\t " +- else +- elements="${elements}, " +- fi ++ elements="${elements},\\n\\t\\t\\t " + done + done + echo -e "$elements" +diff --git a/tests/shell/testcases/maps/0008interval_map_delete_0 b/tests/shell/testcases/maps/0008interval_map_delete_0 +index 39ea312..86e54b6 100755 +--- a/tests/shell/testcases/maps/0008interval_map_delete_0 ++++ b/tests/shell/testcases/maps/0008interval_map_delete_0 +@@ -6,7 +6,8 @@ EXPECTED="table ip filter { + map m { + type ipv4_addr : mark + flags interval +- elements = { 127.0.0.2 : 0x00000002, 127.0.0.3 : 0x00000003 } ++ elements = { 127.0.0.2 : 0x00000002, ++ 127.0.0.3 : 0x00000003 } + } + + chain input { +diff --git a/tests/shell/testcases/maps/dumps/0003map_add_many_elements_0.nft b/tests/shell/testcases/maps/dumps/0003map_add_many_elements_0.nft +index c651af0..bd6e05d 100644 +--- a/tests/shell/testcases/maps/dumps/0003map_add_many_elements_0.nft ++++ b/tests/shell/testcases/maps/dumps/0003map_add_many_elements_0.nft +@@ -1,486 +1,966 @@ + table ip x { + map y { + type ipv4_addr : ipv4_addr +- elements = { 10.0.1.1 : 10.0.1.1, 10.0.1.2 : 10.0.1.2, +- 10.0.1.3 : 10.0.1.3, 10.0.1.4 : 10.0.1.4, +- 10.0.1.5 : 10.0.1.5, 10.0.1.6 : 10.0.1.6, +- 10.0.1.7 : 10.0.1.7, 10.0.1.8 : 10.0.1.8, +- 10.0.1.9 : 10.0.1.9, 10.0.1.10 : 10.0.1.10, +- 10.0.1.11 : 10.0.1.11, 10.0.1.12 : 10.0.1.12, +- 10.0.1.13 : 10.0.1.13, 10.0.1.14 : 10.0.1.14, +- 10.0.1.15 : 10.0.1.15, 10.0.1.16 : 10.0.1.16, +- 10.0.1.17 : 10.0.1.17, 10.0.1.18 : 10.0.1.18, +- 10.0.1.19 : 10.0.1.19, 10.0.1.20 : 10.0.1.20, +- 10.0.1.21 : 10.0.1.21, 10.0.1.22 : 10.0.1.22, +- 10.0.1.23 : 10.0.1.23, 10.0.1.24 : 10.0.1.24, +- 10.0.1.25 : 10.0.1.25, 10.0.1.26 : 10.0.1.26, +- 10.0.1.27 : 10.0.1.27, 10.0.1.28 : 10.0.1.28, +- 10.0.1.29 : 10.0.1.29, 10.0.1.30 : 10.0.1.30, +- 10.0.1.31 : 10.0.1.31, 10.0.2.1 : 10.0.2.1, +- 10.0.2.2 : 10.0.2.2, 10.0.2.3 : 10.0.2.3, +- 10.0.2.4 : 10.0.2.4, 10.0.2.5 : 10.0.2.5, +- 10.0.2.6 : 10.0.2.6, 10.0.2.7 : 10.0.2.7, +- 10.0.2.8 : 10.0.2.8, 10.0.2.9 : 10.0.2.9, +- 10.0.2.10 : 10.0.2.10, 10.0.2.11 : 10.0.2.11, +- 10.0.2.12 : 10.0.2.12, 10.0.2.13 : 10.0.2.13, +- 10.0.2.14 : 10.0.2.14, 10.0.2.15 : 10.0.2.15, +- 10.0.2.16 : 10.0.2.16, 10.0.2.17 : 10.0.2.17, +- 10.0.2.18 : 10.0.2.18, 10.0.2.19 : 10.0.2.19, +- 10.0.2.20 : 10.0.2.20, 10.0.2.21 : 10.0.2.21, +- 10.0.2.22 : 10.0.2.22, 10.0.2.23 : 10.0.2.23, +- 10.0.2.24 : 10.0.2.24, 10.0.2.25 : 10.0.2.25, +- 10.0.2.26 : 10.0.2.26, 10.0.2.27 : 10.0.2.27, +- 10.0.2.28 : 10.0.2.28, 10.0.2.29 : 10.0.2.29, +- 10.0.2.30 : 10.0.2.30, 10.0.2.31 : 10.0.2.31, +- 10.0.3.1 : 10.0.3.1, 10.0.3.2 : 10.0.3.2, +- 10.0.3.3 : 10.0.3.3, 10.0.3.4 : 10.0.3.4, +- 10.0.3.5 : 10.0.3.5, 10.0.3.6 : 10.0.3.6, +- 10.0.3.7 : 10.0.3.7, 10.0.3.8 : 10.0.3.8, +- 10.0.3.9 : 10.0.3.9, 10.0.3.10 : 10.0.3.10, +- 10.0.3.11 : 10.0.3.11, 10.0.3.12 : 10.0.3.12, +- 10.0.3.13 : 10.0.3.13, 10.0.3.14 : 10.0.3.14, +- 10.0.3.15 : 10.0.3.15, 10.0.3.16 : 10.0.3.16, +- 10.0.3.17 : 10.0.3.17, 10.0.3.18 : 10.0.3.18, +- 10.0.3.19 : 10.0.3.19, 10.0.3.20 : 10.0.3.20, +- 10.0.3.21 : 10.0.3.21, 10.0.3.22 : 10.0.3.22, +- 10.0.3.23 : 10.0.3.23, 10.0.3.24 : 10.0.3.24, +- 10.0.3.25 : 10.0.3.25, 10.0.3.26 : 10.0.3.26, +- 10.0.3.27 : 10.0.3.27, 10.0.3.28 : 10.0.3.28, +- 10.0.3.29 : 10.0.3.29, 10.0.3.30 : 10.0.3.30, +- 10.0.3.31 : 10.0.3.31, 10.0.4.1 : 10.0.4.1, +- 10.0.4.2 : 10.0.4.2, 10.0.4.3 : 10.0.4.3, +- 10.0.4.4 : 10.0.4.4, 10.0.4.5 : 10.0.4.5, +- 10.0.4.6 : 10.0.4.6, 10.0.4.7 : 10.0.4.7, +- 10.0.4.8 : 10.0.4.8, 10.0.4.9 : 10.0.4.9, +- 10.0.4.10 : 10.0.4.10, 10.0.4.11 : 10.0.4.11, +- 10.0.4.12 : 10.0.4.12, 10.0.4.13 : 10.0.4.13, +- 10.0.4.14 : 10.0.4.14, 10.0.4.15 : 10.0.4.15, +- 10.0.4.16 : 10.0.4.16, 10.0.4.17 : 10.0.4.17, +- 10.0.4.18 : 10.0.4.18, 10.0.4.19 : 10.0.4.19, +- 10.0.4.20 : 10.0.4.20, 10.0.4.21 : 10.0.4.21, +- 10.0.4.22 : 10.0.4.22, 10.0.4.23 : 10.0.4.23, +- 10.0.4.24 : 10.0.4.24, 10.0.4.25 : 10.0.4.25, +- 10.0.4.26 : 10.0.4.26, 10.0.4.27 : 10.0.4.27, +- 10.0.4.28 : 10.0.4.28, 10.0.4.29 : 10.0.4.29, +- 10.0.4.30 : 10.0.4.30, 10.0.4.31 : 10.0.4.31, +- 10.0.5.1 : 10.0.5.1, 10.0.5.2 : 10.0.5.2, +- 10.0.5.3 : 10.0.5.3, 10.0.5.4 : 10.0.5.4, +- 10.0.5.5 : 10.0.5.5, 10.0.5.6 : 10.0.5.6, +- 10.0.5.7 : 10.0.5.7, 10.0.5.8 : 10.0.5.8, +- 10.0.5.9 : 10.0.5.9, 10.0.5.10 : 10.0.5.10, +- 10.0.5.11 : 10.0.5.11, 10.0.5.12 : 10.0.5.12, +- 10.0.5.13 : 10.0.5.13, 10.0.5.14 : 10.0.5.14, +- 10.0.5.15 : 10.0.5.15, 10.0.5.16 : 10.0.5.16, +- 10.0.5.17 : 10.0.5.17, 10.0.5.18 : 10.0.5.18, +- 10.0.5.19 : 10.0.5.19, 10.0.5.20 : 10.0.5.20, +- 10.0.5.21 : 10.0.5.21, 10.0.5.22 : 10.0.5.22, +- 10.0.5.23 : 10.0.5.23, 10.0.5.24 : 10.0.5.24, +- 10.0.5.25 : 10.0.5.25, 10.0.5.26 : 10.0.5.26, +- 10.0.5.27 : 10.0.5.27, 10.0.5.28 : 10.0.5.28, +- 10.0.5.29 : 10.0.5.29, 10.0.5.30 : 10.0.5.30, +- 10.0.5.31 : 10.0.5.31, 10.0.6.1 : 10.0.6.1, +- 10.0.6.2 : 10.0.6.2, 10.0.6.3 : 10.0.6.3, +- 10.0.6.4 : 10.0.6.4, 10.0.6.5 : 10.0.6.5, +- 10.0.6.6 : 10.0.6.6, 10.0.6.7 : 10.0.6.7, +- 10.0.6.8 : 10.0.6.8, 10.0.6.9 : 10.0.6.9, +- 10.0.6.10 : 10.0.6.10, 10.0.6.11 : 10.0.6.11, +- 10.0.6.12 : 10.0.6.12, 10.0.6.13 : 10.0.6.13, +- 10.0.6.14 : 10.0.6.14, 10.0.6.15 : 10.0.6.15, +- 10.0.6.16 : 10.0.6.16, 10.0.6.17 : 10.0.6.17, +- 10.0.6.18 : 10.0.6.18, 10.0.6.19 : 10.0.6.19, +- 10.0.6.20 : 10.0.6.20, 10.0.6.21 : 10.0.6.21, +- 10.0.6.22 : 10.0.6.22, 10.0.6.23 : 10.0.6.23, +- 10.0.6.24 : 10.0.6.24, 10.0.6.25 : 10.0.6.25, +- 10.0.6.26 : 10.0.6.26, 10.0.6.27 : 10.0.6.27, +- 10.0.6.28 : 10.0.6.28, 10.0.6.29 : 10.0.6.29, +- 10.0.6.30 : 10.0.6.30, 10.0.6.31 : 10.0.6.31, +- 10.0.7.1 : 10.0.7.1, 10.0.7.2 : 10.0.7.2, +- 10.0.7.3 : 10.0.7.3, 10.0.7.4 : 10.0.7.4, +- 10.0.7.5 : 10.0.7.5, 10.0.7.6 : 10.0.7.6, +- 10.0.7.7 : 10.0.7.7, 10.0.7.8 : 10.0.7.8, +- 10.0.7.9 : 10.0.7.9, 10.0.7.10 : 10.0.7.10, +- 10.0.7.11 : 10.0.7.11, 10.0.7.12 : 10.0.7.12, +- 10.0.7.13 : 10.0.7.13, 10.0.7.14 : 10.0.7.14, +- 10.0.7.15 : 10.0.7.15, 10.0.7.16 : 10.0.7.16, +- 10.0.7.17 : 10.0.7.17, 10.0.7.18 : 10.0.7.18, +- 10.0.7.19 : 10.0.7.19, 10.0.7.20 : 10.0.7.20, +- 10.0.7.21 : 10.0.7.21, 10.0.7.22 : 10.0.7.22, +- 10.0.7.23 : 10.0.7.23, 10.0.7.24 : 10.0.7.24, +- 10.0.7.25 : 10.0.7.25, 10.0.7.26 : 10.0.7.26, +- 10.0.7.27 : 10.0.7.27, 10.0.7.28 : 10.0.7.28, +- 10.0.7.29 : 10.0.7.29, 10.0.7.30 : 10.0.7.30, +- 10.0.7.31 : 10.0.7.31, 10.0.8.1 : 10.0.8.1, +- 10.0.8.2 : 10.0.8.2, 10.0.8.3 : 10.0.8.3, +- 10.0.8.4 : 10.0.8.4, 10.0.8.5 : 10.0.8.5, +- 10.0.8.6 : 10.0.8.6, 10.0.8.7 : 10.0.8.7, +- 10.0.8.8 : 10.0.8.8, 10.0.8.9 : 10.0.8.9, +- 10.0.8.10 : 10.0.8.10, 10.0.8.11 : 10.0.8.11, +- 10.0.8.12 : 10.0.8.12, 10.0.8.13 : 10.0.8.13, +- 10.0.8.14 : 10.0.8.14, 10.0.8.15 : 10.0.8.15, +- 10.0.8.16 : 10.0.8.16, 10.0.8.17 : 10.0.8.17, +- 10.0.8.18 : 10.0.8.18, 10.0.8.19 : 10.0.8.19, +- 10.0.8.20 : 10.0.8.20, 10.0.8.21 : 10.0.8.21, +- 10.0.8.22 : 10.0.8.22, 10.0.8.23 : 10.0.8.23, +- 10.0.8.24 : 10.0.8.24, 10.0.8.25 : 10.0.8.25, +- 10.0.8.26 : 10.0.8.26, 10.0.8.27 : 10.0.8.27, +- 10.0.8.28 : 10.0.8.28, 10.0.8.29 : 10.0.8.29, +- 10.0.8.30 : 10.0.8.30, 10.0.8.31 : 10.0.8.31, +- 10.0.9.1 : 10.0.9.1, 10.0.9.2 : 10.0.9.2, +- 10.0.9.3 : 10.0.9.3, 10.0.9.4 : 10.0.9.4, +- 10.0.9.5 : 10.0.9.5, 10.0.9.6 : 10.0.9.6, +- 10.0.9.7 : 10.0.9.7, 10.0.9.8 : 10.0.9.8, +- 10.0.9.9 : 10.0.9.9, 10.0.9.10 : 10.0.9.10, +- 10.0.9.11 : 10.0.9.11, 10.0.9.12 : 10.0.9.12, +- 10.0.9.13 : 10.0.9.13, 10.0.9.14 : 10.0.9.14, +- 10.0.9.15 : 10.0.9.15, 10.0.9.16 : 10.0.9.16, +- 10.0.9.17 : 10.0.9.17, 10.0.9.18 : 10.0.9.18, +- 10.0.9.19 : 10.0.9.19, 10.0.9.20 : 10.0.9.20, +- 10.0.9.21 : 10.0.9.21, 10.0.9.22 : 10.0.9.22, +- 10.0.9.23 : 10.0.9.23, 10.0.9.24 : 10.0.9.24, +- 10.0.9.25 : 10.0.9.25, 10.0.9.26 : 10.0.9.26, +- 10.0.9.27 : 10.0.9.27, 10.0.9.28 : 10.0.9.28, +- 10.0.9.29 : 10.0.9.29, 10.0.9.30 : 10.0.9.30, +- 10.0.9.31 : 10.0.9.31, 10.0.10.1 : 10.0.10.1, +- 10.0.10.2 : 10.0.10.2, 10.0.10.3 : 10.0.10.3, +- 10.0.10.4 : 10.0.10.4, 10.0.10.5 : 10.0.10.5, +- 10.0.10.6 : 10.0.10.6, 10.0.10.7 : 10.0.10.7, +- 10.0.10.8 : 10.0.10.8, 10.0.10.9 : 10.0.10.9, +- 10.0.10.10 : 10.0.10.10, 10.0.10.11 : 10.0.10.11, +- 10.0.10.12 : 10.0.10.12, 10.0.10.13 : 10.0.10.13, +- 10.0.10.14 : 10.0.10.14, 10.0.10.15 : 10.0.10.15, +- 10.0.10.16 : 10.0.10.16, 10.0.10.17 : 10.0.10.17, +- 10.0.10.18 : 10.0.10.18, 10.0.10.19 : 10.0.10.19, +- 10.0.10.20 : 10.0.10.20, 10.0.10.21 : 10.0.10.21, +- 10.0.10.22 : 10.0.10.22, 10.0.10.23 : 10.0.10.23, +- 10.0.10.24 : 10.0.10.24, 10.0.10.25 : 10.0.10.25, +- 10.0.10.26 : 10.0.10.26, 10.0.10.27 : 10.0.10.27, +- 10.0.10.28 : 10.0.10.28, 10.0.10.29 : 10.0.10.29, +- 10.0.10.30 : 10.0.10.30, 10.0.10.31 : 10.0.10.31, +- 10.0.11.1 : 10.0.11.1, 10.0.11.2 : 10.0.11.2, +- 10.0.11.3 : 10.0.11.3, 10.0.11.4 : 10.0.11.4, +- 10.0.11.5 : 10.0.11.5, 10.0.11.6 : 10.0.11.6, +- 10.0.11.7 : 10.0.11.7, 10.0.11.8 : 10.0.11.8, +- 10.0.11.9 : 10.0.11.9, 10.0.11.10 : 10.0.11.10, +- 10.0.11.11 : 10.0.11.11, 10.0.11.12 : 10.0.11.12, +- 10.0.11.13 : 10.0.11.13, 10.0.11.14 : 10.0.11.14, +- 10.0.11.15 : 10.0.11.15, 10.0.11.16 : 10.0.11.16, +- 10.0.11.17 : 10.0.11.17, 10.0.11.18 : 10.0.11.18, +- 10.0.11.19 : 10.0.11.19, 10.0.11.20 : 10.0.11.20, +- 10.0.11.21 : 10.0.11.21, 10.0.11.22 : 10.0.11.22, +- 10.0.11.23 : 10.0.11.23, 10.0.11.24 : 10.0.11.24, +- 10.0.11.25 : 10.0.11.25, 10.0.11.26 : 10.0.11.26, +- 10.0.11.27 : 10.0.11.27, 10.0.11.28 : 10.0.11.28, +- 10.0.11.29 : 10.0.11.29, 10.0.11.30 : 10.0.11.30, +- 10.0.11.31 : 10.0.11.31, 10.0.12.1 : 10.0.12.1, +- 10.0.12.2 : 10.0.12.2, 10.0.12.3 : 10.0.12.3, +- 10.0.12.4 : 10.0.12.4, 10.0.12.5 : 10.0.12.5, +- 10.0.12.6 : 10.0.12.6, 10.0.12.7 : 10.0.12.7, +- 10.0.12.8 : 10.0.12.8, 10.0.12.9 : 10.0.12.9, +- 10.0.12.10 : 10.0.12.10, 10.0.12.11 : 10.0.12.11, +- 10.0.12.12 : 10.0.12.12, 10.0.12.13 : 10.0.12.13, +- 10.0.12.14 : 10.0.12.14, 10.0.12.15 : 10.0.12.15, +- 10.0.12.16 : 10.0.12.16, 10.0.12.17 : 10.0.12.17, +- 10.0.12.18 : 10.0.12.18, 10.0.12.19 : 10.0.12.19, +- 10.0.12.20 : 10.0.12.20, 10.0.12.21 : 10.0.12.21, +- 10.0.12.22 : 10.0.12.22, 10.0.12.23 : 10.0.12.23, +- 10.0.12.24 : 10.0.12.24, 10.0.12.25 : 10.0.12.25, +- 10.0.12.26 : 10.0.12.26, 10.0.12.27 : 10.0.12.27, +- 10.0.12.28 : 10.0.12.28, 10.0.12.29 : 10.0.12.29, +- 10.0.12.30 : 10.0.12.30, 10.0.12.31 : 10.0.12.31, +- 10.0.13.1 : 10.0.13.1, 10.0.13.2 : 10.0.13.2, +- 10.0.13.3 : 10.0.13.3, 10.0.13.4 : 10.0.13.4, +- 10.0.13.5 : 10.0.13.5, 10.0.13.6 : 10.0.13.6, +- 10.0.13.7 : 10.0.13.7, 10.0.13.8 : 10.0.13.8, +- 10.0.13.9 : 10.0.13.9, 10.0.13.10 : 10.0.13.10, +- 10.0.13.11 : 10.0.13.11, 10.0.13.12 : 10.0.13.12, +- 10.0.13.13 : 10.0.13.13, 10.0.13.14 : 10.0.13.14, +- 10.0.13.15 : 10.0.13.15, 10.0.13.16 : 10.0.13.16, +- 10.0.13.17 : 10.0.13.17, 10.0.13.18 : 10.0.13.18, +- 10.0.13.19 : 10.0.13.19, 10.0.13.20 : 10.0.13.20, +- 10.0.13.21 : 10.0.13.21, 10.0.13.22 : 10.0.13.22, +- 10.0.13.23 : 10.0.13.23, 10.0.13.24 : 10.0.13.24, +- 10.0.13.25 : 10.0.13.25, 10.0.13.26 : 10.0.13.26, +- 10.0.13.27 : 10.0.13.27, 10.0.13.28 : 10.0.13.28, +- 10.0.13.29 : 10.0.13.29, 10.0.13.30 : 10.0.13.30, +- 10.0.13.31 : 10.0.13.31, 10.0.14.1 : 10.0.14.1, +- 10.0.14.2 : 10.0.14.2, 10.0.14.3 : 10.0.14.3, +- 10.0.14.4 : 10.0.14.4, 10.0.14.5 : 10.0.14.5, +- 10.0.14.6 : 10.0.14.6, 10.0.14.7 : 10.0.14.7, +- 10.0.14.8 : 10.0.14.8, 10.0.14.9 : 10.0.14.9, +- 10.0.14.10 : 10.0.14.10, 10.0.14.11 : 10.0.14.11, +- 10.0.14.12 : 10.0.14.12, 10.0.14.13 : 10.0.14.13, +- 10.0.14.14 : 10.0.14.14, 10.0.14.15 : 10.0.14.15, +- 10.0.14.16 : 10.0.14.16, 10.0.14.17 : 10.0.14.17, +- 10.0.14.18 : 10.0.14.18, 10.0.14.19 : 10.0.14.19, +- 10.0.14.20 : 10.0.14.20, 10.0.14.21 : 10.0.14.21, +- 10.0.14.22 : 10.0.14.22, 10.0.14.23 : 10.0.14.23, +- 10.0.14.24 : 10.0.14.24, 10.0.14.25 : 10.0.14.25, +- 10.0.14.26 : 10.0.14.26, 10.0.14.27 : 10.0.14.27, +- 10.0.14.28 : 10.0.14.28, 10.0.14.29 : 10.0.14.29, +- 10.0.14.30 : 10.0.14.30, 10.0.14.31 : 10.0.14.31, +- 10.0.15.1 : 10.0.15.1, 10.0.15.2 : 10.0.15.2, +- 10.0.15.3 : 10.0.15.3, 10.0.15.4 : 10.0.15.4, +- 10.0.15.5 : 10.0.15.5, 10.0.15.6 : 10.0.15.6, +- 10.0.15.7 : 10.0.15.7, 10.0.15.8 : 10.0.15.8, +- 10.0.15.9 : 10.0.15.9, 10.0.15.10 : 10.0.15.10, +- 10.0.15.11 : 10.0.15.11, 10.0.15.12 : 10.0.15.12, +- 10.0.15.13 : 10.0.15.13, 10.0.15.14 : 10.0.15.14, +- 10.0.15.15 : 10.0.15.15, 10.0.15.16 : 10.0.15.16, +- 10.0.15.17 : 10.0.15.17, 10.0.15.18 : 10.0.15.18, +- 10.0.15.19 : 10.0.15.19, 10.0.15.20 : 10.0.15.20, +- 10.0.15.21 : 10.0.15.21, 10.0.15.22 : 10.0.15.22, +- 10.0.15.23 : 10.0.15.23, 10.0.15.24 : 10.0.15.24, +- 10.0.15.25 : 10.0.15.25, 10.0.15.26 : 10.0.15.26, +- 10.0.15.27 : 10.0.15.27, 10.0.15.28 : 10.0.15.28, +- 10.0.15.29 : 10.0.15.29, 10.0.15.30 : 10.0.15.30, +- 10.0.15.31 : 10.0.15.31, 10.0.16.1 : 10.0.16.1, +- 10.0.16.2 : 10.0.16.2, 10.0.16.3 : 10.0.16.3, +- 10.0.16.4 : 10.0.16.4, 10.0.16.5 : 10.0.16.5, +- 10.0.16.6 : 10.0.16.6, 10.0.16.7 : 10.0.16.7, +- 10.0.16.8 : 10.0.16.8, 10.0.16.9 : 10.0.16.9, +- 10.0.16.10 : 10.0.16.10, 10.0.16.11 : 10.0.16.11, +- 10.0.16.12 : 10.0.16.12, 10.0.16.13 : 10.0.16.13, +- 10.0.16.14 : 10.0.16.14, 10.0.16.15 : 10.0.16.15, +- 10.0.16.16 : 10.0.16.16, 10.0.16.17 : 10.0.16.17, +- 10.0.16.18 : 10.0.16.18, 10.0.16.19 : 10.0.16.19, +- 10.0.16.20 : 10.0.16.20, 10.0.16.21 : 10.0.16.21, +- 10.0.16.22 : 10.0.16.22, 10.0.16.23 : 10.0.16.23, +- 10.0.16.24 : 10.0.16.24, 10.0.16.25 : 10.0.16.25, +- 10.0.16.26 : 10.0.16.26, 10.0.16.27 : 10.0.16.27, +- 10.0.16.28 : 10.0.16.28, 10.0.16.29 : 10.0.16.29, +- 10.0.16.30 : 10.0.16.30, 10.0.16.31 : 10.0.16.31, +- 10.0.17.1 : 10.0.17.1, 10.0.17.2 : 10.0.17.2, +- 10.0.17.3 : 10.0.17.3, 10.0.17.4 : 10.0.17.4, +- 10.0.17.5 : 10.0.17.5, 10.0.17.6 : 10.0.17.6, +- 10.0.17.7 : 10.0.17.7, 10.0.17.8 : 10.0.17.8, +- 10.0.17.9 : 10.0.17.9, 10.0.17.10 : 10.0.17.10, +- 10.0.17.11 : 10.0.17.11, 10.0.17.12 : 10.0.17.12, +- 10.0.17.13 : 10.0.17.13, 10.0.17.14 : 10.0.17.14, +- 10.0.17.15 : 10.0.17.15, 10.0.17.16 : 10.0.17.16, +- 10.0.17.17 : 10.0.17.17, 10.0.17.18 : 10.0.17.18, +- 10.0.17.19 : 10.0.17.19, 10.0.17.20 : 10.0.17.20, +- 10.0.17.21 : 10.0.17.21, 10.0.17.22 : 10.0.17.22, +- 10.0.17.23 : 10.0.17.23, 10.0.17.24 : 10.0.17.24, +- 10.0.17.25 : 10.0.17.25, 10.0.17.26 : 10.0.17.26, +- 10.0.17.27 : 10.0.17.27, 10.0.17.28 : 10.0.17.28, +- 10.0.17.29 : 10.0.17.29, 10.0.17.30 : 10.0.17.30, +- 10.0.17.31 : 10.0.17.31, 10.0.18.1 : 10.0.18.1, +- 10.0.18.2 : 10.0.18.2, 10.0.18.3 : 10.0.18.3, +- 10.0.18.4 : 10.0.18.4, 10.0.18.5 : 10.0.18.5, +- 10.0.18.6 : 10.0.18.6, 10.0.18.7 : 10.0.18.7, +- 10.0.18.8 : 10.0.18.8, 10.0.18.9 : 10.0.18.9, +- 10.0.18.10 : 10.0.18.10, 10.0.18.11 : 10.0.18.11, +- 10.0.18.12 : 10.0.18.12, 10.0.18.13 : 10.0.18.13, +- 10.0.18.14 : 10.0.18.14, 10.0.18.15 : 10.0.18.15, +- 10.0.18.16 : 10.0.18.16, 10.0.18.17 : 10.0.18.17, +- 10.0.18.18 : 10.0.18.18, 10.0.18.19 : 10.0.18.19, +- 10.0.18.20 : 10.0.18.20, 10.0.18.21 : 10.0.18.21, +- 10.0.18.22 : 10.0.18.22, 10.0.18.23 : 10.0.18.23, +- 10.0.18.24 : 10.0.18.24, 10.0.18.25 : 10.0.18.25, +- 10.0.18.26 : 10.0.18.26, 10.0.18.27 : 10.0.18.27, +- 10.0.18.28 : 10.0.18.28, 10.0.18.29 : 10.0.18.29, +- 10.0.18.30 : 10.0.18.30, 10.0.18.31 : 10.0.18.31, +- 10.0.19.1 : 10.0.19.1, 10.0.19.2 : 10.0.19.2, +- 10.0.19.3 : 10.0.19.3, 10.0.19.4 : 10.0.19.4, +- 10.0.19.5 : 10.0.19.5, 10.0.19.6 : 10.0.19.6, +- 10.0.19.7 : 10.0.19.7, 10.0.19.8 : 10.0.19.8, +- 10.0.19.9 : 10.0.19.9, 10.0.19.10 : 10.0.19.10, +- 10.0.19.11 : 10.0.19.11, 10.0.19.12 : 10.0.19.12, +- 10.0.19.13 : 10.0.19.13, 10.0.19.14 : 10.0.19.14, +- 10.0.19.15 : 10.0.19.15, 10.0.19.16 : 10.0.19.16, +- 10.0.19.17 : 10.0.19.17, 10.0.19.18 : 10.0.19.18, +- 10.0.19.19 : 10.0.19.19, 10.0.19.20 : 10.0.19.20, +- 10.0.19.21 : 10.0.19.21, 10.0.19.22 : 10.0.19.22, +- 10.0.19.23 : 10.0.19.23, 10.0.19.24 : 10.0.19.24, +- 10.0.19.25 : 10.0.19.25, 10.0.19.26 : 10.0.19.26, +- 10.0.19.27 : 10.0.19.27, 10.0.19.28 : 10.0.19.28, +- 10.0.19.29 : 10.0.19.29, 10.0.19.30 : 10.0.19.30, +- 10.0.19.31 : 10.0.19.31, 10.0.20.1 : 10.0.20.1, +- 10.0.20.2 : 10.0.20.2, 10.0.20.3 : 10.0.20.3, +- 10.0.20.4 : 10.0.20.4, 10.0.20.5 : 10.0.20.5, +- 10.0.20.6 : 10.0.20.6, 10.0.20.7 : 10.0.20.7, +- 10.0.20.8 : 10.0.20.8, 10.0.20.9 : 10.0.20.9, +- 10.0.20.10 : 10.0.20.10, 10.0.20.11 : 10.0.20.11, +- 10.0.20.12 : 10.0.20.12, 10.0.20.13 : 10.0.20.13, +- 10.0.20.14 : 10.0.20.14, 10.0.20.15 : 10.0.20.15, +- 10.0.20.16 : 10.0.20.16, 10.0.20.17 : 10.0.20.17, +- 10.0.20.18 : 10.0.20.18, 10.0.20.19 : 10.0.20.19, +- 10.0.20.20 : 10.0.20.20, 10.0.20.21 : 10.0.20.21, +- 10.0.20.22 : 10.0.20.22, 10.0.20.23 : 10.0.20.23, +- 10.0.20.24 : 10.0.20.24, 10.0.20.25 : 10.0.20.25, +- 10.0.20.26 : 10.0.20.26, 10.0.20.27 : 10.0.20.27, +- 10.0.20.28 : 10.0.20.28, 10.0.20.29 : 10.0.20.29, +- 10.0.20.30 : 10.0.20.30, 10.0.20.31 : 10.0.20.31, +- 10.0.21.1 : 10.0.21.1, 10.0.21.2 : 10.0.21.2, +- 10.0.21.3 : 10.0.21.3, 10.0.21.4 : 10.0.21.4, +- 10.0.21.5 : 10.0.21.5, 10.0.21.6 : 10.0.21.6, +- 10.0.21.7 : 10.0.21.7, 10.0.21.8 : 10.0.21.8, +- 10.0.21.9 : 10.0.21.9, 10.0.21.10 : 10.0.21.10, +- 10.0.21.11 : 10.0.21.11, 10.0.21.12 : 10.0.21.12, +- 10.0.21.13 : 10.0.21.13, 10.0.21.14 : 10.0.21.14, +- 10.0.21.15 : 10.0.21.15, 10.0.21.16 : 10.0.21.16, +- 10.0.21.17 : 10.0.21.17, 10.0.21.18 : 10.0.21.18, +- 10.0.21.19 : 10.0.21.19, 10.0.21.20 : 10.0.21.20, +- 10.0.21.21 : 10.0.21.21, 10.0.21.22 : 10.0.21.22, +- 10.0.21.23 : 10.0.21.23, 10.0.21.24 : 10.0.21.24, +- 10.0.21.25 : 10.0.21.25, 10.0.21.26 : 10.0.21.26, +- 10.0.21.27 : 10.0.21.27, 10.0.21.28 : 10.0.21.28, +- 10.0.21.29 : 10.0.21.29, 10.0.21.30 : 10.0.21.30, +- 10.0.21.31 : 10.0.21.31, 10.0.22.1 : 10.0.22.1, +- 10.0.22.2 : 10.0.22.2, 10.0.22.3 : 10.0.22.3, +- 10.0.22.4 : 10.0.22.4, 10.0.22.5 : 10.0.22.5, +- 10.0.22.6 : 10.0.22.6, 10.0.22.7 : 10.0.22.7, +- 10.0.22.8 : 10.0.22.8, 10.0.22.9 : 10.0.22.9, +- 10.0.22.10 : 10.0.22.10, 10.0.22.11 : 10.0.22.11, +- 10.0.22.12 : 10.0.22.12, 10.0.22.13 : 10.0.22.13, +- 10.0.22.14 : 10.0.22.14, 10.0.22.15 : 10.0.22.15, +- 10.0.22.16 : 10.0.22.16, 10.0.22.17 : 10.0.22.17, +- 10.0.22.18 : 10.0.22.18, 10.0.22.19 : 10.0.22.19, +- 10.0.22.20 : 10.0.22.20, 10.0.22.21 : 10.0.22.21, +- 10.0.22.22 : 10.0.22.22, 10.0.22.23 : 10.0.22.23, +- 10.0.22.24 : 10.0.22.24, 10.0.22.25 : 10.0.22.25, +- 10.0.22.26 : 10.0.22.26, 10.0.22.27 : 10.0.22.27, +- 10.0.22.28 : 10.0.22.28, 10.0.22.29 : 10.0.22.29, +- 10.0.22.30 : 10.0.22.30, 10.0.22.31 : 10.0.22.31, +- 10.0.23.1 : 10.0.23.1, 10.0.23.2 : 10.0.23.2, +- 10.0.23.3 : 10.0.23.3, 10.0.23.4 : 10.0.23.4, +- 10.0.23.5 : 10.0.23.5, 10.0.23.6 : 10.0.23.6, +- 10.0.23.7 : 10.0.23.7, 10.0.23.8 : 10.0.23.8, +- 10.0.23.9 : 10.0.23.9, 10.0.23.10 : 10.0.23.10, +- 10.0.23.11 : 10.0.23.11, 10.0.23.12 : 10.0.23.12, +- 10.0.23.13 : 10.0.23.13, 10.0.23.14 : 10.0.23.14, +- 10.0.23.15 : 10.0.23.15, 10.0.23.16 : 10.0.23.16, +- 10.0.23.17 : 10.0.23.17, 10.0.23.18 : 10.0.23.18, +- 10.0.23.19 : 10.0.23.19, 10.0.23.20 : 10.0.23.20, +- 10.0.23.21 : 10.0.23.21, 10.0.23.22 : 10.0.23.22, +- 10.0.23.23 : 10.0.23.23, 10.0.23.24 : 10.0.23.24, +- 10.0.23.25 : 10.0.23.25, 10.0.23.26 : 10.0.23.26, +- 10.0.23.27 : 10.0.23.27, 10.0.23.28 : 10.0.23.28, +- 10.0.23.29 : 10.0.23.29, 10.0.23.30 : 10.0.23.30, +- 10.0.23.31 : 10.0.23.31, 10.0.24.1 : 10.0.24.1, +- 10.0.24.2 : 10.0.24.2, 10.0.24.3 : 10.0.24.3, +- 10.0.24.4 : 10.0.24.4, 10.0.24.5 : 10.0.24.5, +- 10.0.24.6 : 10.0.24.6, 10.0.24.7 : 10.0.24.7, +- 10.0.24.8 : 10.0.24.8, 10.0.24.9 : 10.0.24.9, +- 10.0.24.10 : 10.0.24.10, 10.0.24.11 : 10.0.24.11, +- 10.0.24.12 : 10.0.24.12, 10.0.24.13 : 10.0.24.13, +- 10.0.24.14 : 10.0.24.14, 10.0.24.15 : 10.0.24.15, +- 10.0.24.16 : 10.0.24.16, 10.0.24.17 : 10.0.24.17, +- 10.0.24.18 : 10.0.24.18, 10.0.24.19 : 10.0.24.19, +- 10.0.24.20 : 10.0.24.20, 10.0.24.21 : 10.0.24.21, +- 10.0.24.22 : 10.0.24.22, 10.0.24.23 : 10.0.24.23, +- 10.0.24.24 : 10.0.24.24, 10.0.24.25 : 10.0.24.25, +- 10.0.24.26 : 10.0.24.26, 10.0.24.27 : 10.0.24.27, +- 10.0.24.28 : 10.0.24.28, 10.0.24.29 : 10.0.24.29, +- 10.0.24.30 : 10.0.24.30, 10.0.24.31 : 10.0.24.31, +- 10.0.25.1 : 10.0.25.1, 10.0.25.2 : 10.0.25.2, +- 10.0.25.3 : 10.0.25.3, 10.0.25.4 : 10.0.25.4, +- 10.0.25.5 : 10.0.25.5, 10.0.25.6 : 10.0.25.6, +- 10.0.25.7 : 10.0.25.7, 10.0.25.8 : 10.0.25.8, +- 10.0.25.9 : 10.0.25.9, 10.0.25.10 : 10.0.25.10, +- 10.0.25.11 : 10.0.25.11, 10.0.25.12 : 10.0.25.12, +- 10.0.25.13 : 10.0.25.13, 10.0.25.14 : 10.0.25.14, +- 10.0.25.15 : 10.0.25.15, 10.0.25.16 : 10.0.25.16, +- 10.0.25.17 : 10.0.25.17, 10.0.25.18 : 10.0.25.18, +- 10.0.25.19 : 10.0.25.19, 10.0.25.20 : 10.0.25.20, +- 10.0.25.21 : 10.0.25.21, 10.0.25.22 : 10.0.25.22, +- 10.0.25.23 : 10.0.25.23, 10.0.25.24 : 10.0.25.24, +- 10.0.25.25 : 10.0.25.25, 10.0.25.26 : 10.0.25.26, +- 10.0.25.27 : 10.0.25.27, 10.0.25.28 : 10.0.25.28, +- 10.0.25.29 : 10.0.25.29, 10.0.25.30 : 10.0.25.30, +- 10.0.25.31 : 10.0.25.31, 10.0.26.1 : 10.0.26.1, +- 10.0.26.2 : 10.0.26.2, 10.0.26.3 : 10.0.26.3, +- 10.0.26.4 : 10.0.26.4, 10.0.26.5 : 10.0.26.5, +- 10.0.26.6 : 10.0.26.6, 10.0.26.7 : 10.0.26.7, +- 10.0.26.8 : 10.0.26.8, 10.0.26.9 : 10.0.26.9, +- 10.0.26.10 : 10.0.26.10, 10.0.26.11 : 10.0.26.11, +- 10.0.26.12 : 10.0.26.12, 10.0.26.13 : 10.0.26.13, +- 10.0.26.14 : 10.0.26.14, 10.0.26.15 : 10.0.26.15, +- 10.0.26.16 : 10.0.26.16, 10.0.26.17 : 10.0.26.17, +- 10.0.26.18 : 10.0.26.18, 10.0.26.19 : 10.0.26.19, +- 10.0.26.20 : 10.0.26.20, 10.0.26.21 : 10.0.26.21, +- 10.0.26.22 : 10.0.26.22, 10.0.26.23 : 10.0.26.23, +- 10.0.26.24 : 10.0.26.24, 10.0.26.25 : 10.0.26.25, +- 10.0.26.26 : 10.0.26.26, 10.0.26.27 : 10.0.26.27, +- 10.0.26.28 : 10.0.26.28, 10.0.26.29 : 10.0.26.29, +- 10.0.26.30 : 10.0.26.30, 10.0.26.31 : 10.0.26.31, +- 10.0.27.1 : 10.0.27.1, 10.0.27.2 : 10.0.27.2, +- 10.0.27.3 : 10.0.27.3, 10.0.27.4 : 10.0.27.4, +- 10.0.27.5 : 10.0.27.5, 10.0.27.6 : 10.0.27.6, +- 10.0.27.7 : 10.0.27.7, 10.0.27.8 : 10.0.27.8, +- 10.0.27.9 : 10.0.27.9, 10.0.27.10 : 10.0.27.10, +- 10.0.27.11 : 10.0.27.11, 10.0.27.12 : 10.0.27.12, +- 10.0.27.13 : 10.0.27.13, 10.0.27.14 : 10.0.27.14, +- 10.0.27.15 : 10.0.27.15, 10.0.27.16 : 10.0.27.16, +- 10.0.27.17 : 10.0.27.17, 10.0.27.18 : 10.0.27.18, +- 10.0.27.19 : 10.0.27.19, 10.0.27.20 : 10.0.27.20, +- 10.0.27.21 : 10.0.27.21, 10.0.27.22 : 10.0.27.22, +- 10.0.27.23 : 10.0.27.23, 10.0.27.24 : 10.0.27.24, +- 10.0.27.25 : 10.0.27.25, 10.0.27.26 : 10.0.27.26, +- 10.0.27.27 : 10.0.27.27, 10.0.27.28 : 10.0.27.28, +- 10.0.27.29 : 10.0.27.29, 10.0.27.30 : 10.0.27.30, +- 10.0.27.31 : 10.0.27.31, 10.0.28.1 : 10.0.28.1, +- 10.0.28.2 : 10.0.28.2, 10.0.28.3 : 10.0.28.3, +- 10.0.28.4 : 10.0.28.4, 10.0.28.5 : 10.0.28.5, +- 10.0.28.6 : 10.0.28.6, 10.0.28.7 : 10.0.28.7, +- 10.0.28.8 : 10.0.28.8, 10.0.28.9 : 10.0.28.9, +- 10.0.28.10 : 10.0.28.10, 10.0.28.11 : 10.0.28.11, +- 10.0.28.12 : 10.0.28.12, 10.0.28.13 : 10.0.28.13, +- 10.0.28.14 : 10.0.28.14, 10.0.28.15 : 10.0.28.15, +- 10.0.28.16 : 10.0.28.16, 10.0.28.17 : 10.0.28.17, +- 10.0.28.18 : 10.0.28.18, 10.0.28.19 : 10.0.28.19, +- 10.0.28.20 : 10.0.28.20, 10.0.28.21 : 10.0.28.21, +- 10.0.28.22 : 10.0.28.22, 10.0.28.23 : 10.0.28.23, +- 10.0.28.24 : 10.0.28.24, 10.0.28.25 : 10.0.28.25, +- 10.0.28.26 : 10.0.28.26, 10.0.28.27 : 10.0.28.27, +- 10.0.28.28 : 10.0.28.28, 10.0.28.29 : 10.0.28.29, +- 10.0.28.30 : 10.0.28.30, 10.0.28.31 : 10.0.28.31, +- 10.0.29.1 : 10.0.29.1, 10.0.29.2 : 10.0.29.2, +- 10.0.29.3 : 10.0.29.3, 10.0.29.4 : 10.0.29.4, +- 10.0.29.5 : 10.0.29.5, 10.0.29.6 : 10.0.29.6, +- 10.0.29.7 : 10.0.29.7, 10.0.29.8 : 10.0.29.8, +- 10.0.29.9 : 10.0.29.9, 10.0.29.10 : 10.0.29.10, +- 10.0.29.11 : 10.0.29.11, 10.0.29.12 : 10.0.29.12, +- 10.0.29.13 : 10.0.29.13, 10.0.29.14 : 10.0.29.14, +- 10.0.29.15 : 10.0.29.15, 10.0.29.16 : 10.0.29.16, +- 10.0.29.17 : 10.0.29.17, 10.0.29.18 : 10.0.29.18, +- 10.0.29.19 : 10.0.29.19, 10.0.29.20 : 10.0.29.20, +- 10.0.29.21 : 10.0.29.21, 10.0.29.22 : 10.0.29.22, +- 10.0.29.23 : 10.0.29.23, 10.0.29.24 : 10.0.29.24, +- 10.0.29.25 : 10.0.29.25, 10.0.29.26 : 10.0.29.26, +- 10.0.29.27 : 10.0.29.27, 10.0.29.28 : 10.0.29.28, +- 10.0.29.29 : 10.0.29.29, 10.0.29.30 : 10.0.29.30, +- 10.0.29.31 : 10.0.29.31, 10.0.30.1 : 10.0.30.1, +- 10.0.30.2 : 10.0.30.2, 10.0.30.3 : 10.0.30.3, +- 10.0.30.4 : 10.0.30.4, 10.0.30.5 : 10.0.30.5, +- 10.0.30.6 : 10.0.30.6, 10.0.30.7 : 10.0.30.7, +- 10.0.30.8 : 10.0.30.8, 10.0.30.9 : 10.0.30.9, +- 10.0.30.10 : 10.0.30.10, 10.0.30.11 : 10.0.30.11, +- 10.0.30.12 : 10.0.30.12, 10.0.30.13 : 10.0.30.13, +- 10.0.30.14 : 10.0.30.14, 10.0.30.15 : 10.0.30.15, +- 10.0.30.16 : 10.0.30.16, 10.0.30.17 : 10.0.30.17, +- 10.0.30.18 : 10.0.30.18, 10.0.30.19 : 10.0.30.19, +- 10.0.30.20 : 10.0.30.20, 10.0.30.21 : 10.0.30.21, +- 10.0.30.22 : 10.0.30.22, 10.0.30.23 : 10.0.30.23, +- 10.0.30.24 : 10.0.30.24, 10.0.30.25 : 10.0.30.25, +- 10.0.30.26 : 10.0.30.26, 10.0.30.27 : 10.0.30.27, +- 10.0.30.28 : 10.0.30.28, 10.0.30.29 : 10.0.30.29, +- 10.0.30.30 : 10.0.30.30, 10.0.30.31 : 10.0.30.31, +- 10.0.31.1 : 10.0.31.1, 10.0.31.2 : 10.0.31.2, +- 10.0.31.3 : 10.0.31.3, 10.0.31.4 : 10.0.31.4, +- 10.0.31.5 : 10.0.31.5, 10.0.31.6 : 10.0.31.6, +- 10.0.31.7 : 10.0.31.7, 10.0.31.8 : 10.0.31.8, +- 10.0.31.9 : 10.0.31.9, 10.0.31.10 : 10.0.31.10, +- 10.0.31.11 : 10.0.31.11, 10.0.31.12 : 10.0.31.12, +- 10.0.31.13 : 10.0.31.13, 10.0.31.14 : 10.0.31.14, +- 10.0.31.15 : 10.0.31.15, 10.0.31.16 : 10.0.31.16, +- 10.0.31.17 : 10.0.31.17, 10.0.31.18 : 10.0.31.18, +- 10.0.31.19 : 10.0.31.19, 10.0.31.20 : 10.0.31.20, +- 10.0.31.21 : 10.0.31.21, 10.0.31.22 : 10.0.31.22, +- 10.0.31.23 : 10.0.31.23, 10.0.31.24 : 10.0.31.24, +- 10.0.31.25 : 10.0.31.25, 10.0.31.26 : 10.0.31.26, +- 10.0.31.27 : 10.0.31.27, 10.0.31.28 : 10.0.31.28, +- 10.0.31.29 : 10.0.31.29, 10.0.31.30 : 10.0.31.30, ++ elements = { 10.0.1.1 : 10.0.1.1, ++ 10.0.1.2 : 10.0.1.2, ++ 10.0.1.3 : 10.0.1.3, ++ 10.0.1.4 : 10.0.1.4, ++ 10.0.1.5 : 10.0.1.5, ++ 10.0.1.6 : 10.0.1.6, ++ 10.0.1.7 : 10.0.1.7, ++ 10.0.1.8 : 10.0.1.8, ++ 10.0.1.9 : 10.0.1.9, ++ 10.0.1.10 : 10.0.1.10, ++ 10.0.1.11 : 10.0.1.11, ++ 10.0.1.12 : 10.0.1.12, ++ 10.0.1.13 : 10.0.1.13, ++ 10.0.1.14 : 10.0.1.14, ++ 10.0.1.15 : 10.0.1.15, ++ 10.0.1.16 : 10.0.1.16, ++ 10.0.1.17 : 10.0.1.17, ++ 10.0.1.18 : 10.0.1.18, ++ 10.0.1.19 : 10.0.1.19, ++ 10.0.1.20 : 10.0.1.20, ++ 10.0.1.21 : 10.0.1.21, ++ 10.0.1.22 : 10.0.1.22, ++ 10.0.1.23 : 10.0.1.23, ++ 10.0.1.24 : 10.0.1.24, ++ 10.0.1.25 : 10.0.1.25, ++ 10.0.1.26 : 10.0.1.26, ++ 10.0.1.27 : 10.0.1.27, ++ 10.0.1.28 : 10.0.1.28, ++ 10.0.1.29 : 10.0.1.29, ++ 10.0.1.30 : 10.0.1.30, ++ 10.0.1.31 : 10.0.1.31, ++ 10.0.2.1 : 10.0.2.1, ++ 10.0.2.2 : 10.0.2.2, ++ 10.0.2.3 : 10.0.2.3, ++ 10.0.2.4 : 10.0.2.4, ++ 10.0.2.5 : 10.0.2.5, ++ 10.0.2.6 : 10.0.2.6, ++ 10.0.2.7 : 10.0.2.7, ++ 10.0.2.8 : 10.0.2.8, ++ 10.0.2.9 : 10.0.2.9, ++ 10.0.2.10 : 10.0.2.10, ++ 10.0.2.11 : 10.0.2.11, ++ 10.0.2.12 : 10.0.2.12, ++ 10.0.2.13 : 10.0.2.13, ++ 10.0.2.14 : 10.0.2.14, ++ 10.0.2.15 : 10.0.2.15, ++ 10.0.2.16 : 10.0.2.16, ++ 10.0.2.17 : 10.0.2.17, ++ 10.0.2.18 : 10.0.2.18, ++ 10.0.2.19 : 10.0.2.19, ++ 10.0.2.20 : 10.0.2.20, ++ 10.0.2.21 : 10.0.2.21, ++ 10.0.2.22 : 10.0.2.22, ++ 10.0.2.23 : 10.0.2.23, ++ 10.0.2.24 : 10.0.2.24, ++ 10.0.2.25 : 10.0.2.25, ++ 10.0.2.26 : 10.0.2.26, ++ 10.0.2.27 : 10.0.2.27, ++ 10.0.2.28 : 10.0.2.28, ++ 10.0.2.29 : 10.0.2.29, ++ 10.0.2.30 : 10.0.2.30, ++ 10.0.2.31 : 10.0.2.31, ++ 10.0.3.1 : 10.0.3.1, ++ 10.0.3.2 : 10.0.3.2, ++ 10.0.3.3 : 10.0.3.3, ++ 10.0.3.4 : 10.0.3.4, ++ 10.0.3.5 : 10.0.3.5, ++ 10.0.3.6 : 10.0.3.6, ++ 10.0.3.7 : 10.0.3.7, ++ 10.0.3.8 : 10.0.3.8, ++ 10.0.3.9 : 10.0.3.9, ++ 10.0.3.10 : 10.0.3.10, ++ 10.0.3.11 : 10.0.3.11, ++ 10.0.3.12 : 10.0.3.12, ++ 10.0.3.13 : 10.0.3.13, ++ 10.0.3.14 : 10.0.3.14, ++ 10.0.3.15 : 10.0.3.15, ++ 10.0.3.16 : 10.0.3.16, ++ 10.0.3.17 : 10.0.3.17, ++ 10.0.3.18 : 10.0.3.18, ++ 10.0.3.19 : 10.0.3.19, ++ 10.0.3.20 : 10.0.3.20, ++ 10.0.3.21 : 10.0.3.21, ++ 10.0.3.22 : 10.0.3.22, ++ 10.0.3.23 : 10.0.3.23, ++ 10.0.3.24 : 10.0.3.24, ++ 10.0.3.25 : 10.0.3.25, ++ 10.0.3.26 : 10.0.3.26, ++ 10.0.3.27 : 10.0.3.27, ++ 10.0.3.28 : 10.0.3.28, ++ 10.0.3.29 : 10.0.3.29, ++ 10.0.3.30 : 10.0.3.30, ++ 10.0.3.31 : 10.0.3.31, ++ 10.0.4.1 : 10.0.4.1, ++ 10.0.4.2 : 10.0.4.2, ++ 10.0.4.3 : 10.0.4.3, ++ 10.0.4.4 : 10.0.4.4, ++ 10.0.4.5 : 10.0.4.5, ++ 10.0.4.6 : 10.0.4.6, ++ 10.0.4.7 : 10.0.4.7, ++ 10.0.4.8 : 10.0.4.8, ++ 10.0.4.9 : 10.0.4.9, ++ 10.0.4.10 : 10.0.4.10, ++ 10.0.4.11 : 10.0.4.11, ++ 10.0.4.12 : 10.0.4.12, ++ 10.0.4.13 : 10.0.4.13, ++ 10.0.4.14 : 10.0.4.14, ++ 10.0.4.15 : 10.0.4.15, ++ 10.0.4.16 : 10.0.4.16, ++ 10.0.4.17 : 10.0.4.17, ++ 10.0.4.18 : 10.0.4.18, ++ 10.0.4.19 : 10.0.4.19, ++ 10.0.4.20 : 10.0.4.20, ++ 10.0.4.21 : 10.0.4.21, ++ 10.0.4.22 : 10.0.4.22, ++ 10.0.4.23 : 10.0.4.23, ++ 10.0.4.24 : 10.0.4.24, ++ 10.0.4.25 : 10.0.4.25, ++ 10.0.4.26 : 10.0.4.26, ++ 10.0.4.27 : 10.0.4.27, ++ 10.0.4.28 : 10.0.4.28, ++ 10.0.4.29 : 10.0.4.29, ++ 10.0.4.30 : 10.0.4.30, ++ 10.0.4.31 : 10.0.4.31, ++ 10.0.5.1 : 10.0.5.1, ++ 10.0.5.2 : 10.0.5.2, ++ 10.0.5.3 : 10.0.5.3, ++ 10.0.5.4 : 10.0.5.4, ++ 10.0.5.5 : 10.0.5.5, ++ 10.0.5.6 : 10.0.5.6, ++ 10.0.5.7 : 10.0.5.7, ++ 10.0.5.8 : 10.0.5.8, ++ 10.0.5.9 : 10.0.5.9, ++ 10.0.5.10 : 10.0.5.10, ++ 10.0.5.11 : 10.0.5.11, ++ 10.0.5.12 : 10.0.5.12, ++ 10.0.5.13 : 10.0.5.13, ++ 10.0.5.14 : 10.0.5.14, ++ 10.0.5.15 : 10.0.5.15, ++ 10.0.5.16 : 10.0.5.16, ++ 10.0.5.17 : 10.0.5.17, ++ 10.0.5.18 : 10.0.5.18, ++ 10.0.5.19 : 10.0.5.19, ++ 10.0.5.20 : 10.0.5.20, ++ 10.0.5.21 : 10.0.5.21, ++ 10.0.5.22 : 10.0.5.22, ++ 10.0.5.23 : 10.0.5.23, ++ 10.0.5.24 : 10.0.5.24, ++ 10.0.5.25 : 10.0.5.25, ++ 10.0.5.26 : 10.0.5.26, ++ 10.0.5.27 : 10.0.5.27, ++ 10.0.5.28 : 10.0.5.28, ++ 10.0.5.29 : 10.0.5.29, ++ 10.0.5.30 : 10.0.5.30, ++ 10.0.5.31 : 10.0.5.31, ++ 10.0.6.1 : 10.0.6.1, ++ 10.0.6.2 : 10.0.6.2, ++ 10.0.6.3 : 10.0.6.3, ++ 10.0.6.4 : 10.0.6.4, ++ 10.0.6.5 : 10.0.6.5, ++ 10.0.6.6 : 10.0.6.6, ++ 10.0.6.7 : 10.0.6.7, ++ 10.0.6.8 : 10.0.6.8, ++ 10.0.6.9 : 10.0.6.9, ++ 10.0.6.10 : 10.0.6.10, ++ 10.0.6.11 : 10.0.6.11, ++ 10.0.6.12 : 10.0.6.12, ++ 10.0.6.13 : 10.0.6.13, ++ 10.0.6.14 : 10.0.6.14, ++ 10.0.6.15 : 10.0.6.15, ++ 10.0.6.16 : 10.0.6.16, ++ 10.0.6.17 : 10.0.6.17, ++ 10.0.6.18 : 10.0.6.18, ++ 10.0.6.19 : 10.0.6.19, ++ 10.0.6.20 : 10.0.6.20, ++ 10.0.6.21 : 10.0.6.21, ++ 10.0.6.22 : 10.0.6.22, ++ 10.0.6.23 : 10.0.6.23, ++ 10.0.6.24 : 10.0.6.24, ++ 10.0.6.25 : 10.0.6.25, ++ 10.0.6.26 : 10.0.6.26, ++ 10.0.6.27 : 10.0.6.27, ++ 10.0.6.28 : 10.0.6.28, ++ 10.0.6.29 : 10.0.6.29, ++ 10.0.6.30 : 10.0.6.30, ++ 10.0.6.31 : 10.0.6.31, ++ 10.0.7.1 : 10.0.7.1, ++ 10.0.7.2 : 10.0.7.2, ++ 10.0.7.3 : 10.0.7.3, ++ 10.0.7.4 : 10.0.7.4, ++ 10.0.7.5 : 10.0.7.5, ++ 10.0.7.6 : 10.0.7.6, ++ 10.0.7.7 : 10.0.7.7, ++ 10.0.7.8 : 10.0.7.8, ++ 10.0.7.9 : 10.0.7.9, ++ 10.0.7.10 : 10.0.7.10, ++ 10.0.7.11 : 10.0.7.11, ++ 10.0.7.12 : 10.0.7.12, ++ 10.0.7.13 : 10.0.7.13, ++ 10.0.7.14 : 10.0.7.14, ++ 10.0.7.15 : 10.0.7.15, ++ 10.0.7.16 : 10.0.7.16, ++ 10.0.7.17 : 10.0.7.17, ++ 10.0.7.18 : 10.0.7.18, ++ 10.0.7.19 : 10.0.7.19, ++ 10.0.7.20 : 10.0.7.20, ++ 10.0.7.21 : 10.0.7.21, ++ 10.0.7.22 : 10.0.7.22, ++ 10.0.7.23 : 10.0.7.23, ++ 10.0.7.24 : 10.0.7.24, ++ 10.0.7.25 : 10.0.7.25, ++ 10.0.7.26 : 10.0.7.26, ++ 10.0.7.27 : 10.0.7.27, ++ 10.0.7.28 : 10.0.7.28, ++ 10.0.7.29 : 10.0.7.29, ++ 10.0.7.30 : 10.0.7.30, ++ 10.0.7.31 : 10.0.7.31, ++ 10.0.8.1 : 10.0.8.1, ++ 10.0.8.2 : 10.0.8.2, ++ 10.0.8.3 : 10.0.8.3, ++ 10.0.8.4 : 10.0.8.4, ++ 10.0.8.5 : 10.0.8.5, ++ 10.0.8.6 : 10.0.8.6, ++ 10.0.8.7 : 10.0.8.7, ++ 10.0.8.8 : 10.0.8.8, ++ 10.0.8.9 : 10.0.8.9, ++ 10.0.8.10 : 10.0.8.10, ++ 10.0.8.11 : 10.0.8.11, ++ 10.0.8.12 : 10.0.8.12, ++ 10.0.8.13 : 10.0.8.13, ++ 10.0.8.14 : 10.0.8.14, ++ 10.0.8.15 : 10.0.8.15, ++ 10.0.8.16 : 10.0.8.16, ++ 10.0.8.17 : 10.0.8.17, ++ 10.0.8.18 : 10.0.8.18, ++ 10.0.8.19 : 10.0.8.19, ++ 10.0.8.20 : 10.0.8.20, ++ 10.0.8.21 : 10.0.8.21, ++ 10.0.8.22 : 10.0.8.22, ++ 10.0.8.23 : 10.0.8.23, ++ 10.0.8.24 : 10.0.8.24, ++ 10.0.8.25 : 10.0.8.25, ++ 10.0.8.26 : 10.0.8.26, ++ 10.0.8.27 : 10.0.8.27, ++ 10.0.8.28 : 10.0.8.28, ++ 10.0.8.29 : 10.0.8.29, ++ 10.0.8.30 : 10.0.8.30, ++ 10.0.8.31 : 10.0.8.31, ++ 10.0.9.1 : 10.0.9.1, ++ 10.0.9.2 : 10.0.9.2, ++ 10.0.9.3 : 10.0.9.3, ++ 10.0.9.4 : 10.0.9.4, ++ 10.0.9.5 : 10.0.9.5, ++ 10.0.9.6 : 10.0.9.6, ++ 10.0.9.7 : 10.0.9.7, ++ 10.0.9.8 : 10.0.9.8, ++ 10.0.9.9 : 10.0.9.9, ++ 10.0.9.10 : 10.0.9.10, ++ 10.0.9.11 : 10.0.9.11, ++ 10.0.9.12 : 10.0.9.12, ++ 10.0.9.13 : 10.0.9.13, ++ 10.0.9.14 : 10.0.9.14, ++ 10.0.9.15 : 10.0.9.15, ++ 10.0.9.16 : 10.0.9.16, ++ 10.0.9.17 : 10.0.9.17, ++ 10.0.9.18 : 10.0.9.18, ++ 10.0.9.19 : 10.0.9.19, ++ 10.0.9.20 : 10.0.9.20, ++ 10.0.9.21 : 10.0.9.21, ++ 10.0.9.22 : 10.0.9.22, ++ 10.0.9.23 : 10.0.9.23, ++ 10.0.9.24 : 10.0.9.24, ++ 10.0.9.25 : 10.0.9.25, ++ 10.0.9.26 : 10.0.9.26, ++ 10.0.9.27 : 10.0.9.27, ++ 10.0.9.28 : 10.0.9.28, ++ 10.0.9.29 : 10.0.9.29, ++ 10.0.9.30 : 10.0.9.30, ++ 10.0.9.31 : 10.0.9.31, ++ 10.0.10.1 : 10.0.10.1, ++ 10.0.10.2 : 10.0.10.2, ++ 10.0.10.3 : 10.0.10.3, ++ 10.0.10.4 : 10.0.10.4, ++ 10.0.10.5 : 10.0.10.5, ++ 10.0.10.6 : 10.0.10.6, ++ 10.0.10.7 : 10.0.10.7, ++ 10.0.10.8 : 10.0.10.8, ++ 10.0.10.9 : 10.0.10.9, ++ 10.0.10.10 : 10.0.10.10, ++ 10.0.10.11 : 10.0.10.11, ++ 10.0.10.12 : 10.0.10.12, ++ 10.0.10.13 : 10.0.10.13, ++ 10.0.10.14 : 10.0.10.14, ++ 10.0.10.15 : 10.0.10.15, ++ 10.0.10.16 : 10.0.10.16, ++ 10.0.10.17 : 10.0.10.17, ++ 10.0.10.18 : 10.0.10.18, ++ 10.0.10.19 : 10.0.10.19, ++ 10.0.10.20 : 10.0.10.20, ++ 10.0.10.21 : 10.0.10.21, ++ 10.0.10.22 : 10.0.10.22, ++ 10.0.10.23 : 10.0.10.23, ++ 10.0.10.24 : 10.0.10.24, ++ 10.0.10.25 : 10.0.10.25, ++ 10.0.10.26 : 10.0.10.26, ++ 10.0.10.27 : 10.0.10.27, ++ 10.0.10.28 : 10.0.10.28, ++ 10.0.10.29 : 10.0.10.29, ++ 10.0.10.30 : 10.0.10.30, ++ 10.0.10.31 : 10.0.10.31, ++ 10.0.11.1 : 10.0.11.1, ++ 10.0.11.2 : 10.0.11.2, ++ 10.0.11.3 : 10.0.11.3, ++ 10.0.11.4 : 10.0.11.4, ++ 10.0.11.5 : 10.0.11.5, ++ 10.0.11.6 : 10.0.11.6, ++ 10.0.11.7 : 10.0.11.7, ++ 10.0.11.8 : 10.0.11.8, ++ 10.0.11.9 : 10.0.11.9, ++ 10.0.11.10 : 10.0.11.10, ++ 10.0.11.11 : 10.0.11.11, ++ 10.0.11.12 : 10.0.11.12, ++ 10.0.11.13 : 10.0.11.13, ++ 10.0.11.14 : 10.0.11.14, ++ 10.0.11.15 : 10.0.11.15, ++ 10.0.11.16 : 10.0.11.16, ++ 10.0.11.17 : 10.0.11.17, ++ 10.0.11.18 : 10.0.11.18, ++ 10.0.11.19 : 10.0.11.19, ++ 10.0.11.20 : 10.0.11.20, ++ 10.0.11.21 : 10.0.11.21, ++ 10.0.11.22 : 10.0.11.22, ++ 10.0.11.23 : 10.0.11.23, ++ 10.0.11.24 : 10.0.11.24, ++ 10.0.11.25 : 10.0.11.25, ++ 10.0.11.26 : 10.0.11.26, ++ 10.0.11.27 : 10.0.11.27, ++ 10.0.11.28 : 10.0.11.28, ++ 10.0.11.29 : 10.0.11.29, ++ 10.0.11.30 : 10.0.11.30, ++ 10.0.11.31 : 10.0.11.31, ++ 10.0.12.1 : 10.0.12.1, ++ 10.0.12.2 : 10.0.12.2, ++ 10.0.12.3 : 10.0.12.3, ++ 10.0.12.4 : 10.0.12.4, ++ 10.0.12.5 : 10.0.12.5, ++ 10.0.12.6 : 10.0.12.6, ++ 10.0.12.7 : 10.0.12.7, ++ 10.0.12.8 : 10.0.12.8, ++ 10.0.12.9 : 10.0.12.9, ++ 10.0.12.10 : 10.0.12.10, ++ 10.0.12.11 : 10.0.12.11, ++ 10.0.12.12 : 10.0.12.12, ++ 10.0.12.13 : 10.0.12.13, ++ 10.0.12.14 : 10.0.12.14, ++ 10.0.12.15 : 10.0.12.15, ++ 10.0.12.16 : 10.0.12.16, ++ 10.0.12.17 : 10.0.12.17, ++ 10.0.12.18 : 10.0.12.18, ++ 10.0.12.19 : 10.0.12.19, ++ 10.0.12.20 : 10.0.12.20, ++ 10.0.12.21 : 10.0.12.21, ++ 10.0.12.22 : 10.0.12.22, ++ 10.0.12.23 : 10.0.12.23, ++ 10.0.12.24 : 10.0.12.24, ++ 10.0.12.25 : 10.0.12.25, ++ 10.0.12.26 : 10.0.12.26, ++ 10.0.12.27 : 10.0.12.27, ++ 10.0.12.28 : 10.0.12.28, ++ 10.0.12.29 : 10.0.12.29, ++ 10.0.12.30 : 10.0.12.30, ++ 10.0.12.31 : 10.0.12.31, ++ 10.0.13.1 : 10.0.13.1, ++ 10.0.13.2 : 10.0.13.2, ++ 10.0.13.3 : 10.0.13.3, ++ 10.0.13.4 : 10.0.13.4, ++ 10.0.13.5 : 10.0.13.5, ++ 10.0.13.6 : 10.0.13.6, ++ 10.0.13.7 : 10.0.13.7, ++ 10.0.13.8 : 10.0.13.8, ++ 10.0.13.9 : 10.0.13.9, ++ 10.0.13.10 : 10.0.13.10, ++ 10.0.13.11 : 10.0.13.11, ++ 10.0.13.12 : 10.0.13.12, ++ 10.0.13.13 : 10.0.13.13, ++ 10.0.13.14 : 10.0.13.14, ++ 10.0.13.15 : 10.0.13.15, ++ 10.0.13.16 : 10.0.13.16, ++ 10.0.13.17 : 10.0.13.17, ++ 10.0.13.18 : 10.0.13.18, ++ 10.0.13.19 : 10.0.13.19, ++ 10.0.13.20 : 10.0.13.20, ++ 10.0.13.21 : 10.0.13.21, ++ 10.0.13.22 : 10.0.13.22, ++ 10.0.13.23 : 10.0.13.23, ++ 10.0.13.24 : 10.0.13.24, ++ 10.0.13.25 : 10.0.13.25, ++ 10.0.13.26 : 10.0.13.26, ++ 10.0.13.27 : 10.0.13.27, ++ 10.0.13.28 : 10.0.13.28, ++ 10.0.13.29 : 10.0.13.29, ++ 10.0.13.30 : 10.0.13.30, ++ 10.0.13.31 : 10.0.13.31, ++ 10.0.14.1 : 10.0.14.1, ++ 10.0.14.2 : 10.0.14.2, ++ 10.0.14.3 : 10.0.14.3, ++ 10.0.14.4 : 10.0.14.4, ++ 10.0.14.5 : 10.0.14.5, ++ 10.0.14.6 : 10.0.14.6, ++ 10.0.14.7 : 10.0.14.7, ++ 10.0.14.8 : 10.0.14.8, ++ 10.0.14.9 : 10.0.14.9, ++ 10.0.14.10 : 10.0.14.10, ++ 10.0.14.11 : 10.0.14.11, ++ 10.0.14.12 : 10.0.14.12, ++ 10.0.14.13 : 10.0.14.13, ++ 10.0.14.14 : 10.0.14.14, ++ 10.0.14.15 : 10.0.14.15, ++ 10.0.14.16 : 10.0.14.16, ++ 10.0.14.17 : 10.0.14.17, ++ 10.0.14.18 : 10.0.14.18, ++ 10.0.14.19 : 10.0.14.19, ++ 10.0.14.20 : 10.0.14.20, ++ 10.0.14.21 : 10.0.14.21, ++ 10.0.14.22 : 10.0.14.22, ++ 10.0.14.23 : 10.0.14.23, ++ 10.0.14.24 : 10.0.14.24, ++ 10.0.14.25 : 10.0.14.25, ++ 10.0.14.26 : 10.0.14.26, ++ 10.0.14.27 : 10.0.14.27, ++ 10.0.14.28 : 10.0.14.28, ++ 10.0.14.29 : 10.0.14.29, ++ 10.0.14.30 : 10.0.14.30, ++ 10.0.14.31 : 10.0.14.31, ++ 10.0.15.1 : 10.0.15.1, ++ 10.0.15.2 : 10.0.15.2, ++ 10.0.15.3 : 10.0.15.3, ++ 10.0.15.4 : 10.0.15.4, ++ 10.0.15.5 : 10.0.15.5, ++ 10.0.15.6 : 10.0.15.6, ++ 10.0.15.7 : 10.0.15.7, ++ 10.0.15.8 : 10.0.15.8, ++ 10.0.15.9 : 10.0.15.9, ++ 10.0.15.10 : 10.0.15.10, ++ 10.0.15.11 : 10.0.15.11, ++ 10.0.15.12 : 10.0.15.12, ++ 10.0.15.13 : 10.0.15.13, ++ 10.0.15.14 : 10.0.15.14, ++ 10.0.15.15 : 10.0.15.15, ++ 10.0.15.16 : 10.0.15.16, ++ 10.0.15.17 : 10.0.15.17, ++ 10.0.15.18 : 10.0.15.18, ++ 10.0.15.19 : 10.0.15.19, ++ 10.0.15.20 : 10.0.15.20, ++ 10.0.15.21 : 10.0.15.21, ++ 10.0.15.22 : 10.0.15.22, ++ 10.0.15.23 : 10.0.15.23, ++ 10.0.15.24 : 10.0.15.24, ++ 10.0.15.25 : 10.0.15.25, ++ 10.0.15.26 : 10.0.15.26, ++ 10.0.15.27 : 10.0.15.27, ++ 10.0.15.28 : 10.0.15.28, ++ 10.0.15.29 : 10.0.15.29, ++ 10.0.15.30 : 10.0.15.30, ++ 10.0.15.31 : 10.0.15.31, ++ 10.0.16.1 : 10.0.16.1, ++ 10.0.16.2 : 10.0.16.2, ++ 10.0.16.3 : 10.0.16.3, ++ 10.0.16.4 : 10.0.16.4, ++ 10.0.16.5 : 10.0.16.5, ++ 10.0.16.6 : 10.0.16.6, ++ 10.0.16.7 : 10.0.16.7, ++ 10.0.16.8 : 10.0.16.8, ++ 10.0.16.9 : 10.0.16.9, ++ 10.0.16.10 : 10.0.16.10, ++ 10.0.16.11 : 10.0.16.11, ++ 10.0.16.12 : 10.0.16.12, ++ 10.0.16.13 : 10.0.16.13, ++ 10.0.16.14 : 10.0.16.14, ++ 10.0.16.15 : 10.0.16.15, ++ 10.0.16.16 : 10.0.16.16, ++ 10.0.16.17 : 10.0.16.17, ++ 10.0.16.18 : 10.0.16.18, ++ 10.0.16.19 : 10.0.16.19, ++ 10.0.16.20 : 10.0.16.20, ++ 10.0.16.21 : 10.0.16.21, ++ 10.0.16.22 : 10.0.16.22, ++ 10.0.16.23 : 10.0.16.23, ++ 10.0.16.24 : 10.0.16.24, ++ 10.0.16.25 : 10.0.16.25, ++ 10.0.16.26 : 10.0.16.26, ++ 10.0.16.27 : 10.0.16.27, ++ 10.0.16.28 : 10.0.16.28, ++ 10.0.16.29 : 10.0.16.29, ++ 10.0.16.30 : 10.0.16.30, ++ 10.0.16.31 : 10.0.16.31, ++ 10.0.17.1 : 10.0.17.1, ++ 10.0.17.2 : 10.0.17.2, ++ 10.0.17.3 : 10.0.17.3, ++ 10.0.17.4 : 10.0.17.4, ++ 10.0.17.5 : 10.0.17.5, ++ 10.0.17.6 : 10.0.17.6, ++ 10.0.17.7 : 10.0.17.7, ++ 10.0.17.8 : 10.0.17.8, ++ 10.0.17.9 : 10.0.17.9, ++ 10.0.17.10 : 10.0.17.10, ++ 10.0.17.11 : 10.0.17.11, ++ 10.0.17.12 : 10.0.17.12, ++ 10.0.17.13 : 10.0.17.13, ++ 10.0.17.14 : 10.0.17.14, ++ 10.0.17.15 : 10.0.17.15, ++ 10.0.17.16 : 10.0.17.16, ++ 10.0.17.17 : 10.0.17.17, ++ 10.0.17.18 : 10.0.17.18, ++ 10.0.17.19 : 10.0.17.19, ++ 10.0.17.20 : 10.0.17.20, ++ 10.0.17.21 : 10.0.17.21, ++ 10.0.17.22 : 10.0.17.22, ++ 10.0.17.23 : 10.0.17.23, ++ 10.0.17.24 : 10.0.17.24, ++ 10.0.17.25 : 10.0.17.25, ++ 10.0.17.26 : 10.0.17.26, ++ 10.0.17.27 : 10.0.17.27, ++ 10.0.17.28 : 10.0.17.28, ++ 10.0.17.29 : 10.0.17.29, ++ 10.0.17.30 : 10.0.17.30, ++ 10.0.17.31 : 10.0.17.31, ++ 10.0.18.1 : 10.0.18.1, ++ 10.0.18.2 : 10.0.18.2, ++ 10.0.18.3 : 10.0.18.3, ++ 10.0.18.4 : 10.0.18.4, ++ 10.0.18.5 : 10.0.18.5, ++ 10.0.18.6 : 10.0.18.6, ++ 10.0.18.7 : 10.0.18.7, ++ 10.0.18.8 : 10.0.18.8, ++ 10.0.18.9 : 10.0.18.9, ++ 10.0.18.10 : 10.0.18.10, ++ 10.0.18.11 : 10.0.18.11, ++ 10.0.18.12 : 10.0.18.12, ++ 10.0.18.13 : 10.0.18.13, ++ 10.0.18.14 : 10.0.18.14, ++ 10.0.18.15 : 10.0.18.15, ++ 10.0.18.16 : 10.0.18.16, ++ 10.0.18.17 : 10.0.18.17, ++ 10.0.18.18 : 10.0.18.18, ++ 10.0.18.19 : 10.0.18.19, ++ 10.0.18.20 : 10.0.18.20, ++ 10.0.18.21 : 10.0.18.21, ++ 10.0.18.22 : 10.0.18.22, ++ 10.0.18.23 : 10.0.18.23, ++ 10.0.18.24 : 10.0.18.24, ++ 10.0.18.25 : 10.0.18.25, ++ 10.0.18.26 : 10.0.18.26, ++ 10.0.18.27 : 10.0.18.27, ++ 10.0.18.28 : 10.0.18.28, ++ 10.0.18.29 : 10.0.18.29, ++ 10.0.18.30 : 10.0.18.30, ++ 10.0.18.31 : 10.0.18.31, ++ 10.0.19.1 : 10.0.19.1, ++ 10.0.19.2 : 10.0.19.2, ++ 10.0.19.3 : 10.0.19.3, ++ 10.0.19.4 : 10.0.19.4, ++ 10.0.19.5 : 10.0.19.5, ++ 10.0.19.6 : 10.0.19.6, ++ 10.0.19.7 : 10.0.19.7, ++ 10.0.19.8 : 10.0.19.8, ++ 10.0.19.9 : 10.0.19.9, ++ 10.0.19.10 : 10.0.19.10, ++ 10.0.19.11 : 10.0.19.11, ++ 10.0.19.12 : 10.0.19.12, ++ 10.0.19.13 : 10.0.19.13, ++ 10.0.19.14 : 10.0.19.14, ++ 10.0.19.15 : 10.0.19.15, ++ 10.0.19.16 : 10.0.19.16, ++ 10.0.19.17 : 10.0.19.17, ++ 10.0.19.18 : 10.0.19.18, ++ 10.0.19.19 : 10.0.19.19, ++ 10.0.19.20 : 10.0.19.20, ++ 10.0.19.21 : 10.0.19.21, ++ 10.0.19.22 : 10.0.19.22, ++ 10.0.19.23 : 10.0.19.23, ++ 10.0.19.24 : 10.0.19.24, ++ 10.0.19.25 : 10.0.19.25, ++ 10.0.19.26 : 10.0.19.26, ++ 10.0.19.27 : 10.0.19.27, ++ 10.0.19.28 : 10.0.19.28, ++ 10.0.19.29 : 10.0.19.29, ++ 10.0.19.30 : 10.0.19.30, ++ 10.0.19.31 : 10.0.19.31, ++ 10.0.20.1 : 10.0.20.1, ++ 10.0.20.2 : 10.0.20.2, ++ 10.0.20.3 : 10.0.20.3, ++ 10.0.20.4 : 10.0.20.4, ++ 10.0.20.5 : 10.0.20.5, ++ 10.0.20.6 : 10.0.20.6, ++ 10.0.20.7 : 10.0.20.7, ++ 10.0.20.8 : 10.0.20.8, ++ 10.0.20.9 : 10.0.20.9, ++ 10.0.20.10 : 10.0.20.10, ++ 10.0.20.11 : 10.0.20.11, ++ 10.0.20.12 : 10.0.20.12, ++ 10.0.20.13 : 10.0.20.13, ++ 10.0.20.14 : 10.0.20.14, ++ 10.0.20.15 : 10.0.20.15, ++ 10.0.20.16 : 10.0.20.16, ++ 10.0.20.17 : 10.0.20.17, ++ 10.0.20.18 : 10.0.20.18, ++ 10.0.20.19 : 10.0.20.19, ++ 10.0.20.20 : 10.0.20.20, ++ 10.0.20.21 : 10.0.20.21, ++ 10.0.20.22 : 10.0.20.22, ++ 10.0.20.23 : 10.0.20.23, ++ 10.0.20.24 : 10.0.20.24, ++ 10.0.20.25 : 10.0.20.25, ++ 10.0.20.26 : 10.0.20.26, ++ 10.0.20.27 : 10.0.20.27, ++ 10.0.20.28 : 10.0.20.28, ++ 10.0.20.29 : 10.0.20.29, ++ 10.0.20.30 : 10.0.20.30, ++ 10.0.20.31 : 10.0.20.31, ++ 10.0.21.1 : 10.0.21.1, ++ 10.0.21.2 : 10.0.21.2, ++ 10.0.21.3 : 10.0.21.3, ++ 10.0.21.4 : 10.0.21.4, ++ 10.0.21.5 : 10.0.21.5, ++ 10.0.21.6 : 10.0.21.6, ++ 10.0.21.7 : 10.0.21.7, ++ 10.0.21.8 : 10.0.21.8, ++ 10.0.21.9 : 10.0.21.9, ++ 10.0.21.10 : 10.0.21.10, ++ 10.0.21.11 : 10.0.21.11, ++ 10.0.21.12 : 10.0.21.12, ++ 10.0.21.13 : 10.0.21.13, ++ 10.0.21.14 : 10.0.21.14, ++ 10.0.21.15 : 10.0.21.15, ++ 10.0.21.16 : 10.0.21.16, ++ 10.0.21.17 : 10.0.21.17, ++ 10.0.21.18 : 10.0.21.18, ++ 10.0.21.19 : 10.0.21.19, ++ 10.0.21.20 : 10.0.21.20, ++ 10.0.21.21 : 10.0.21.21, ++ 10.0.21.22 : 10.0.21.22, ++ 10.0.21.23 : 10.0.21.23, ++ 10.0.21.24 : 10.0.21.24, ++ 10.0.21.25 : 10.0.21.25, ++ 10.0.21.26 : 10.0.21.26, ++ 10.0.21.27 : 10.0.21.27, ++ 10.0.21.28 : 10.0.21.28, ++ 10.0.21.29 : 10.0.21.29, ++ 10.0.21.30 : 10.0.21.30, ++ 10.0.21.31 : 10.0.21.31, ++ 10.0.22.1 : 10.0.22.1, ++ 10.0.22.2 : 10.0.22.2, ++ 10.0.22.3 : 10.0.22.3, ++ 10.0.22.4 : 10.0.22.4, ++ 10.0.22.5 : 10.0.22.5, ++ 10.0.22.6 : 10.0.22.6, ++ 10.0.22.7 : 10.0.22.7, ++ 10.0.22.8 : 10.0.22.8, ++ 10.0.22.9 : 10.0.22.9, ++ 10.0.22.10 : 10.0.22.10, ++ 10.0.22.11 : 10.0.22.11, ++ 10.0.22.12 : 10.0.22.12, ++ 10.0.22.13 : 10.0.22.13, ++ 10.0.22.14 : 10.0.22.14, ++ 10.0.22.15 : 10.0.22.15, ++ 10.0.22.16 : 10.0.22.16, ++ 10.0.22.17 : 10.0.22.17, ++ 10.0.22.18 : 10.0.22.18, ++ 10.0.22.19 : 10.0.22.19, ++ 10.0.22.20 : 10.0.22.20, ++ 10.0.22.21 : 10.0.22.21, ++ 10.0.22.22 : 10.0.22.22, ++ 10.0.22.23 : 10.0.22.23, ++ 10.0.22.24 : 10.0.22.24, ++ 10.0.22.25 : 10.0.22.25, ++ 10.0.22.26 : 10.0.22.26, ++ 10.0.22.27 : 10.0.22.27, ++ 10.0.22.28 : 10.0.22.28, ++ 10.0.22.29 : 10.0.22.29, ++ 10.0.22.30 : 10.0.22.30, ++ 10.0.22.31 : 10.0.22.31, ++ 10.0.23.1 : 10.0.23.1, ++ 10.0.23.2 : 10.0.23.2, ++ 10.0.23.3 : 10.0.23.3, ++ 10.0.23.4 : 10.0.23.4, ++ 10.0.23.5 : 10.0.23.5, ++ 10.0.23.6 : 10.0.23.6, ++ 10.0.23.7 : 10.0.23.7, ++ 10.0.23.8 : 10.0.23.8, ++ 10.0.23.9 : 10.0.23.9, ++ 10.0.23.10 : 10.0.23.10, ++ 10.0.23.11 : 10.0.23.11, ++ 10.0.23.12 : 10.0.23.12, ++ 10.0.23.13 : 10.0.23.13, ++ 10.0.23.14 : 10.0.23.14, ++ 10.0.23.15 : 10.0.23.15, ++ 10.0.23.16 : 10.0.23.16, ++ 10.0.23.17 : 10.0.23.17, ++ 10.0.23.18 : 10.0.23.18, ++ 10.0.23.19 : 10.0.23.19, ++ 10.0.23.20 : 10.0.23.20, ++ 10.0.23.21 : 10.0.23.21, ++ 10.0.23.22 : 10.0.23.22, ++ 10.0.23.23 : 10.0.23.23, ++ 10.0.23.24 : 10.0.23.24, ++ 10.0.23.25 : 10.0.23.25, ++ 10.0.23.26 : 10.0.23.26, ++ 10.0.23.27 : 10.0.23.27, ++ 10.0.23.28 : 10.0.23.28, ++ 10.0.23.29 : 10.0.23.29, ++ 10.0.23.30 : 10.0.23.30, ++ 10.0.23.31 : 10.0.23.31, ++ 10.0.24.1 : 10.0.24.1, ++ 10.0.24.2 : 10.0.24.2, ++ 10.0.24.3 : 10.0.24.3, ++ 10.0.24.4 : 10.0.24.4, ++ 10.0.24.5 : 10.0.24.5, ++ 10.0.24.6 : 10.0.24.6, ++ 10.0.24.7 : 10.0.24.7, ++ 10.0.24.8 : 10.0.24.8, ++ 10.0.24.9 : 10.0.24.9, ++ 10.0.24.10 : 10.0.24.10, ++ 10.0.24.11 : 10.0.24.11, ++ 10.0.24.12 : 10.0.24.12, ++ 10.0.24.13 : 10.0.24.13, ++ 10.0.24.14 : 10.0.24.14, ++ 10.0.24.15 : 10.0.24.15, ++ 10.0.24.16 : 10.0.24.16, ++ 10.0.24.17 : 10.0.24.17, ++ 10.0.24.18 : 10.0.24.18, ++ 10.0.24.19 : 10.0.24.19, ++ 10.0.24.20 : 10.0.24.20, ++ 10.0.24.21 : 10.0.24.21, ++ 10.0.24.22 : 10.0.24.22, ++ 10.0.24.23 : 10.0.24.23, ++ 10.0.24.24 : 10.0.24.24, ++ 10.0.24.25 : 10.0.24.25, ++ 10.0.24.26 : 10.0.24.26, ++ 10.0.24.27 : 10.0.24.27, ++ 10.0.24.28 : 10.0.24.28, ++ 10.0.24.29 : 10.0.24.29, ++ 10.0.24.30 : 10.0.24.30, ++ 10.0.24.31 : 10.0.24.31, ++ 10.0.25.1 : 10.0.25.1, ++ 10.0.25.2 : 10.0.25.2, ++ 10.0.25.3 : 10.0.25.3, ++ 10.0.25.4 : 10.0.25.4, ++ 10.0.25.5 : 10.0.25.5, ++ 10.0.25.6 : 10.0.25.6, ++ 10.0.25.7 : 10.0.25.7, ++ 10.0.25.8 : 10.0.25.8, ++ 10.0.25.9 : 10.0.25.9, ++ 10.0.25.10 : 10.0.25.10, ++ 10.0.25.11 : 10.0.25.11, ++ 10.0.25.12 : 10.0.25.12, ++ 10.0.25.13 : 10.0.25.13, ++ 10.0.25.14 : 10.0.25.14, ++ 10.0.25.15 : 10.0.25.15, ++ 10.0.25.16 : 10.0.25.16, ++ 10.0.25.17 : 10.0.25.17, ++ 10.0.25.18 : 10.0.25.18, ++ 10.0.25.19 : 10.0.25.19, ++ 10.0.25.20 : 10.0.25.20, ++ 10.0.25.21 : 10.0.25.21, ++ 10.0.25.22 : 10.0.25.22, ++ 10.0.25.23 : 10.0.25.23, ++ 10.0.25.24 : 10.0.25.24, ++ 10.0.25.25 : 10.0.25.25, ++ 10.0.25.26 : 10.0.25.26, ++ 10.0.25.27 : 10.0.25.27, ++ 10.0.25.28 : 10.0.25.28, ++ 10.0.25.29 : 10.0.25.29, ++ 10.0.25.30 : 10.0.25.30, ++ 10.0.25.31 : 10.0.25.31, ++ 10.0.26.1 : 10.0.26.1, ++ 10.0.26.2 : 10.0.26.2, ++ 10.0.26.3 : 10.0.26.3, ++ 10.0.26.4 : 10.0.26.4, ++ 10.0.26.5 : 10.0.26.5, ++ 10.0.26.6 : 10.0.26.6, ++ 10.0.26.7 : 10.0.26.7, ++ 10.0.26.8 : 10.0.26.8, ++ 10.0.26.9 : 10.0.26.9, ++ 10.0.26.10 : 10.0.26.10, ++ 10.0.26.11 : 10.0.26.11, ++ 10.0.26.12 : 10.0.26.12, ++ 10.0.26.13 : 10.0.26.13, ++ 10.0.26.14 : 10.0.26.14, ++ 10.0.26.15 : 10.0.26.15, ++ 10.0.26.16 : 10.0.26.16, ++ 10.0.26.17 : 10.0.26.17, ++ 10.0.26.18 : 10.0.26.18, ++ 10.0.26.19 : 10.0.26.19, ++ 10.0.26.20 : 10.0.26.20, ++ 10.0.26.21 : 10.0.26.21, ++ 10.0.26.22 : 10.0.26.22, ++ 10.0.26.23 : 10.0.26.23, ++ 10.0.26.24 : 10.0.26.24, ++ 10.0.26.25 : 10.0.26.25, ++ 10.0.26.26 : 10.0.26.26, ++ 10.0.26.27 : 10.0.26.27, ++ 10.0.26.28 : 10.0.26.28, ++ 10.0.26.29 : 10.0.26.29, ++ 10.0.26.30 : 10.0.26.30, ++ 10.0.26.31 : 10.0.26.31, ++ 10.0.27.1 : 10.0.27.1, ++ 10.0.27.2 : 10.0.27.2, ++ 10.0.27.3 : 10.0.27.3, ++ 10.0.27.4 : 10.0.27.4, ++ 10.0.27.5 : 10.0.27.5, ++ 10.0.27.6 : 10.0.27.6, ++ 10.0.27.7 : 10.0.27.7, ++ 10.0.27.8 : 10.0.27.8, ++ 10.0.27.9 : 10.0.27.9, ++ 10.0.27.10 : 10.0.27.10, ++ 10.0.27.11 : 10.0.27.11, ++ 10.0.27.12 : 10.0.27.12, ++ 10.0.27.13 : 10.0.27.13, ++ 10.0.27.14 : 10.0.27.14, ++ 10.0.27.15 : 10.0.27.15, ++ 10.0.27.16 : 10.0.27.16, ++ 10.0.27.17 : 10.0.27.17, ++ 10.0.27.18 : 10.0.27.18, ++ 10.0.27.19 : 10.0.27.19, ++ 10.0.27.20 : 10.0.27.20, ++ 10.0.27.21 : 10.0.27.21, ++ 10.0.27.22 : 10.0.27.22, ++ 10.0.27.23 : 10.0.27.23, ++ 10.0.27.24 : 10.0.27.24, ++ 10.0.27.25 : 10.0.27.25, ++ 10.0.27.26 : 10.0.27.26, ++ 10.0.27.27 : 10.0.27.27, ++ 10.0.27.28 : 10.0.27.28, ++ 10.0.27.29 : 10.0.27.29, ++ 10.0.27.30 : 10.0.27.30, ++ 10.0.27.31 : 10.0.27.31, ++ 10.0.28.1 : 10.0.28.1, ++ 10.0.28.2 : 10.0.28.2, ++ 10.0.28.3 : 10.0.28.3, ++ 10.0.28.4 : 10.0.28.4, ++ 10.0.28.5 : 10.0.28.5, ++ 10.0.28.6 : 10.0.28.6, ++ 10.0.28.7 : 10.0.28.7, ++ 10.0.28.8 : 10.0.28.8, ++ 10.0.28.9 : 10.0.28.9, ++ 10.0.28.10 : 10.0.28.10, ++ 10.0.28.11 : 10.0.28.11, ++ 10.0.28.12 : 10.0.28.12, ++ 10.0.28.13 : 10.0.28.13, ++ 10.0.28.14 : 10.0.28.14, ++ 10.0.28.15 : 10.0.28.15, ++ 10.0.28.16 : 10.0.28.16, ++ 10.0.28.17 : 10.0.28.17, ++ 10.0.28.18 : 10.0.28.18, ++ 10.0.28.19 : 10.0.28.19, ++ 10.0.28.20 : 10.0.28.20, ++ 10.0.28.21 : 10.0.28.21, ++ 10.0.28.22 : 10.0.28.22, ++ 10.0.28.23 : 10.0.28.23, ++ 10.0.28.24 : 10.0.28.24, ++ 10.0.28.25 : 10.0.28.25, ++ 10.0.28.26 : 10.0.28.26, ++ 10.0.28.27 : 10.0.28.27, ++ 10.0.28.28 : 10.0.28.28, ++ 10.0.28.29 : 10.0.28.29, ++ 10.0.28.30 : 10.0.28.30, ++ 10.0.28.31 : 10.0.28.31, ++ 10.0.29.1 : 10.0.29.1, ++ 10.0.29.2 : 10.0.29.2, ++ 10.0.29.3 : 10.0.29.3, ++ 10.0.29.4 : 10.0.29.4, ++ 10.0.29.5 : 10.0.29.5, ++ 10.0.29.6 : 10.0.29.6, ++ 10.0.29.7 : 10.0.29.7, ++ 10.0.29.8 : 10.0.29.8, ++ 10.0.29.9 : 10.0.29.9, ++ 10.0.29.10 : 10.0.29.10, ++ 10.0.29.11 : 10.0.29.11, ++ 10.0.29.12 : 10.0.29.12, ++ 10.0.29.13 : 10.0.29.13, ++ 10.0.29.14 : 10.0.29.14, ++ 10.0.29.15 : 10.0.29.15, ++ 10.0.29.16 : 10.0.29.16, ++ 10.0.29.17 : 10.0.29.17, ++ 10.0.29.18 : 10.0.29.18, ++ 10.0.29.19 : 10.0.29.19, ++ 10.0.29.20 : 10.0.29.20, ++ 10.0.29.21 : 10.0.29.21, ++ 10.0.29.22 : 10.0.29.22, ++ 10.0.29.23 : 10.0.29.23, ++ 10.0.29.24 : 10.0.29.24, ++ 10.0.29.25 : 10.0.29.25, ++ 10.0.29.26 : 10.0.29.26, ++ 10.0.29.27 : 10.0.29.27, ++ 10.0.29.28 : 10.0.29.28, ++ 10.0.29.29 : 10.0.29.29, ++ 10.0.29.30 : 10.0.29.30, ++ 10.0.29.31 : 10.0.29.31, ++ 10.0.30.1 : 10.0.30.1, ++ 10.0.30.2 : 10.0.30.2, ++ 10.0.30.3 : 10.0.30.3, ++ 10.0.30.4 : 10.0.30.4, ++ 10.0.30.5 : 10.0.30.5, ++ 10.0.30.6 : 10.0.30.6, ++ 10.0.30.7 : 10.0.30.7, ++ 10.0.30.8 : 10.0.30.8, ++ 10.0.30.9 : 10.0.30.9, ++ 10.0.30.10 : 10.0.30.10, ++ 10.0.30.11 : 10.0.30.11, ++ 10.0.30.12 : 10.0.30.12, ++ 10.0.30.13 : 10.0.30.13, ++ 10.0.30.14 : 10.0.30.14, ++ 10.0.30.15 : 10.0.30.15, ++ 10.0.30.16 : 10.0.30.16, ++ 10.0.30.17 : 10.0.30.17, ++ 10.0.30.18 : 10.0.30.18, ++ 10.0.30.19 : 10.0.30.19, ++ 10.0.30.20 : 10.0.30.20, ++ 10.0.30.21 : 10.0.30.21, ++ 10.0.30.22 : 10.0.30.22, ++ 10.0.30.23 : 10.0.30.23, ++ 10.0.30.24 : 10.0.30.24, ++ 10.0.30.25 : 10.0.30.25, ++ 10.0.30.26 : 10.0.30.26, ++ 10.0.30.27 : 10.0.30.27, ++ 10.0.30.28 : 10.0.30.28, ++ 10.0.30.29 : 10.0.30.29, ++ 10.0.30.30 : 10.0.30.30, ++ 10.0.30.31 : 10.0.30.31, ++ 10.0.31.1 : 10.0.31.1, ++ 10.0.31.2 : 10.0.31.2, ++ 10.0.31.3 : 10.0.31.3, ++ 10.0.31.4 : 10.0.31.4, ++ 10.0.31.5 : 10.0.31.5, ++ 10.0.31.6 : 10.0.31.6, ++ 10.0.31.7 : 10.0.31.7, ++ 10.0.31.8 : 10.0.31.8, ++ 10.0.31.9 : 10.0.31.9, ++ 10.0.31.10 : 10.0.31.10, ++ 10.0.31.11 : 10.0.31.11, ++ 10.0.31.12 : 10.0.31.12, ++ 10.0.31.13 : 10.0.31.13, ++ 10.0.31.14 : 10.0.31.14, ++ 10.0.31.15 : 10.0.31.15, ++ 10.0.31.16 : 10.0.31.16, ++ 10.0.31.17 : 10.0.31.17, ++ 10.0.31.18 : 10.0.31.18, ++ 10.0.31.19 : 10.0.31.19, ++ 10.0.31.20 : 10.0.31.20, ++ 10.0.31.21 : 10.0.31.21, ++ 10.0.31.22 : 10.0.31.22, ++ 10.0.31.23 : 10.0.31.23, ++ 10.0.31.24 : 10.0.31.24, ++ 10.0.31.25 : 10.0.31.25, ++ 10.0.31.26 : 10.0.31.26, ++ 10.0.31.27 : 10.0.31.27, ++ 10.0.31.28 : 10.0.31.28, ++ 10.0.31.29 : 10.0.31.29, ++ 10.0.31.30 : 10.0.31.30, + 10.0.31.31 : 10.0.31.31 } + } + } +diff --git a/tests/shell/testcases/maps/dumps/0005interval_map_add_many_elements_0.nft b/tests/shell/testcases/maps/dumps/0005interval_map_add_many_elements_0.nft +index ab992c4..b1e017b 100644 +--- a/tests/shell/testcases/maps/dumps/0005interval_map_add_many_elements_0.nft ++++ b/tests/shell/testcases/maps/dumps/0005interval_map_add_many_elements_0.nft +@@ -2,7 +2,9 @@ table ip x { + map y { + type ipv4_addr : ipv4_addr + flags interval +- elements = { 10.1.1.0/24 : 10.0.1.1, 10.1.2.0/24 : 10.0.1.2, +- 10.2.1.0/24 : 10.0.2.1, 10.2.2.0/24 : 10.0.2.2 } ++ elements = { 10.1.1.0/24 : 10.0.1.1, ++ 10.1.2.0/24 : 10.0.1.2, ++ 10.2.1.0/24 : 10.0.2.1, ++ 10.2.2.0/24 : 10.0.2.2 } + } + } +diff --git a/tests/shell/testcases/maps/dumps/0006interval_map_overlap_0.nft b/tests/shell/testcases/maps/dumps/0006interval_map_overlap_0.nft +index 1f5343f..74380c2 100644 +--- a/tests/shell/testcases/maps/dumps/0006interval_map_overlap_0.nft ++++ b/tests/shell/testcases/maps/dumps/0006interval_map_overlap_0.nft +@@ -2,6 +2,7 @@ table ip x { + map y { + type ipv4_addr : ipv4_addr + flags interval +- elements = { 10.0.1.0/24 : 10.0.0.1, 10.0.2.0/24 : 10.0.0.2 } ++ elements = { 10.0.1.0/24 : 10.0.0.1, ++ 10.0.2.0/24 : 10.0.0.2 } + } + } +diff --git a/tests/shell/testcases/maps/dumps/0008interval_map_delete_0.nft b/tests/shell/testcases/maps/dumps/0008interval_map_delete_0.nft +index a470a34..1b199ff 100644 +--- a/tests/shell/testcases/maps/dumps/0008interval_map_delete_0.nft ++++ b/tests/shell/testcases/maps/dumps/0008interval_map_delete_0.nft +@@ -2,7 +2,8 @@ table ip filter { + map m { + type ipv4_addr : mark + flags interval +- elements = { 127.0.0.2 : 0x00000002, 127.0.0.3 : 0x00000003 } ++ elements = { 127.0.0.2 : 0x00000002, ++ 127.0.0.3 : 0x00000003 } + } + + chain input { +diff --git a/tests/shell/testcases/maps/dumps/0011vmap_0.nft b/tests/shell/testcases/maps/dumps/0011vmap_0.nft +index 4a72b5e..94b85a6 100644 +--- a/tests/shell/testcases/maps/dumps/0011vmap_0.nft ++++ b/tests/shell/testcases/maps/dumps/0011vmap_0.nft +@@ -2,7 +2,8 @@ table inet filter { + map portmap { + type inet_service : verdict + counter +- elements = { 22 counter packets 0 bytes 0 : jump ssh_input, * counter packets 0 bytes 0 : drop } ++ elements = { 22 counter packets 0 bytes 0 : jump ssh_input, ++ * counter packets 0 bytes 0 : drop } + } + + chain ssh_input { +diff --git a/tests/shell/testcases/maps/dumps/0017_map_variable_0.nft b/tests/shell/testcases/maps/dumps/0017_map_variable_0.nft +index 796dd72..f6d7f6a 100644 +--- a/tests/shell/testcases/maps/dumps/0017_map_variable_0.nft ++++ b/tests/shell/testcases/maps/dumps/0017_map_variable_0.nft +@@ -1,11 +1,13 @@ + table ip x { + map y { + typeof ip saddr : meta mark +- elements = { 1.1.1.1 : 0x00000002, * : 0x00000003 } ++ elements = { 1.1.1.1 : 0x00000002, ++ * : 0x00000003 } + } + + map z { + typeof ip saddr : meta mark +- elements = { 1.1.1.1 : 0x00000002, * : 0x00000003 } ++ elements = { 1.1.1.1 : 0x00000002, ++ * : 0x00000003 } + } + } +diff --git a/tests/shell/testcases/maps/dumps/typeof_maps_0.nft b/tests/shell/testcases/maps/dumps/typeof_maps_0.nft +index a5c0a60..e0efaba 100644 +--- a/tests/shell/testcases/maps/dumps/typeof_maps_0.nft ++++ b/tests/shell/testcases/maps/dumps/typeof_maps_0.nft +@@ -6,7 +6,8 @@ table inet t { + + map m2 { + typeof vlan id : meta mark +- elements = { 1 : 0x00000001, 4095 : 0x00004095 } ++ elements = { 1 : 0x00000001, ++ 4095 : 0x00004095 } + } + + map m3 { +diff --git a/tests/shell/testcases/maps/dumps/vmap_mark_bitwise_0.nft b/tests/shell/testcases/maps/dumps/vmap_mark_bitwise_0.nft +index beb5ffb..6891e86 100644 +--- a/tests/shell/testcases/maps/dumps/vmap_mark_bitwise_0.nft ++++ b/tests/shell/testcases/maps/dumps/vmap_mark_bitwise_0.nft +@@ -5,7 +5,8 @@ table ip x { + + map sctm_o0 { + type mark : verdict +- elements = { 0x00000000 : jump sctm_o0_0, 0x00000001 : jump sctm_o0_1 } ++ elements = { 0x00000000 : jump sctm_o0_0, ++ 0x00000001 : jump sctm_o0_1 } + } + + map sctm_o1 { +diff --git a/tests/shell/testcases/maps/typeof_maps_0 b/tests/shell/testcases/maps/typeof_maps_0 +index 98517fd..d7f45ba 100755 +--- a/tests/shell/testcases/maps/typeof_maps_0 ++++ b/tests/shell/testcases/maps/typeof_maps_0 +@@ -62,7 +62,8 @@ EXPECTED="table inet t { + + map m2 { + typeof vlan id : meta mark +- elements = { 1 : 0x00000001, 4095 : 0x00004095 } ++ elements = { 1 : 0x00000001, ++ 4095 : 0x00004095 } + } + + map m3 { +diff --git a/tests/shell/testcases/sets/dumps/0024named_objects_0.nft b/tests/shell/testcases/sets/dumps/0024named_objects_0.nft +index 2ffa4f2..3188ce2 100644 +--- a/tests/shell/testcases/sets/dumps/0024named_objects_0.nft ++++ b/tests/shell/testcases/sets/dumps/0024named_objects_0.nft +@@ -21,7 +21,8 @@ table inet x { + + map test { + type ipv4_addr : quota +- elements = { 192.168.2.2 : "user124", 192.168.2.3 : "user124" } ++ elements = { 192.168.2.2 : "user124", ++ 192.168.2.3 : "user124" } + } + + chain y { +diff --git a/tests/shell/testcases/sets/dumps/0024synproxy_0.nft b/tests/shell/testcases/sets/dumps/0024synproxy_0.nft +index e0ee86d..dd9a112 100644 +--- a/tests/shell/testcases/sets/dumps/0024synproxy_0.nft ++++ b/tests/shell/testcases/sets/dumps/0024synproxy_0.nft +@@ -13,7 +13,8 @@ table inet x { + map test2 { + type ipv4_addr : synproxy + flags interval +- elements = { 192.168.1.0/24 : "https-synproxy", 192.168.2.0/24 : "other-synproxy" } ++ elements = { 192.168.1.0/24 : "https-synproxy", ++ 192.168.2.0/24 : "other-synproxy" } + } + + chain y { +diff --git a/tests/shell/testcases/sets/dumps/0047nat_0.nft b/tests/shell/testcases/sets/dumps/0047nat_0.nft +index 9fa9fc7..86dbb70 100644 +--- a/tests/shell/testcases/sets/dumps/0047nat_0.nft ++++ b/tests/shell/testcases/sets/dumps/0047nat_0.nft +@@ -2,7 +2,8 @@ table ip x { + map y { + type ipv4_addr : interval ipv4_addr + flags interval +- elements = { 10.141.10.0/24 : 192.168.2.2-192.168.2.4, 10.141.11.0/24 : 192.168.4.2/31, ++ elements = { 10.141.10.0/24 : 192.168.2.2-192.168.2.4, ++ 10.141.11.0/24 : 192.168.4.2/31, + 10.141.12.0/24 : 192.168.5.10-192.168.5.20 } + } + +diff --git a/tests/shell/testcases/sets/dumps/0048set_counters_0.nft b/tests/shell/testcases/sets/dumps/0048set_counters_0.nft +index 2145f6b..d624786 100644 +--- a/tests/shell/testcases/sets/dumps/0048set_counters_0.nft ++++ b/tests/shell/testcases/sets/dumps/0048set_counters_0.nft +@@ -2,7 +2,8 @@ table ip x { + set y { + typeof ip saddr + counter +- elements = { 192.168.10.35 counter packets 0 bytes 0, 192.168.10.101 counter packets 0 bytes 0, ++ elements = { 192.168.10.35 counter packets 0 bytes 0, ++ 192.168.10.101 counter packets 0 bytes 0, + 192.168.10.135 counter packets 0 bytes 0 } + } + +diff --git a/tests/shell/testcases/sets/dumps/0060set_multistmt_0.nft b/tests/shell/testcases/sets/dumps/0060set_multistmt_0.nft +index df68fcd..8521e3f 100644 +--- a/tests/shell/testcases/sets/dumps/0060set_multistmt_0.nft ++++ b/tests/shell/testcases/sets/dumps/0060set_multistmt_0.nft +@@ -2,7 +2,8 @@ table ip x { + set y { + type ipv4_addr + limit rate 1/second burst 5 packets counter +- elements = { 1.1.1.1 limit rate 1/second burst 5 packets counter packets 0 bytes 0, 4.4.4.4 limit rate 1/second burst 5 packets counter packets 0 bytes 0, ++ elements = { 1.1.1.1 limit rate 1/second burst 5 packets counter packets 0 bytes 0, ++ 4.4.4.4 limit rate 1/second burst 5 packets counter packets 0 bytes 0, + 5.5.5.5 limit rate 1/second burst 5 packets counter packets 0 bytes 0 } + } + +diff --git a/tests/shell/testcases/sets/dumps/0060set_multistmt_1.nft b/tests/shell/testcases/sets/dumps/0060set_multistmt_1.nft +index ac1bd26..befc2f7 100644 +--- a/tests/shell/testcases/sets/dumps/0060set_multistmt_1.nft ++++ b/tests/shell/testcases/sets/dumps/0060set_multistmt_1.nft +@@ -4,7 +4,8 @@ table ip x { + size 65535 + flags dynamic + counter quota 500 bytes +- elements = { 1.1.1.1 counter packets 0 bytes 0 quota 500 bytes, 1.2.3.4 counter packets 9 bytes 756 quota 500 bytes used 500 bytes, ++ elements = { 1.1.1.1 counter packets 0 bytes 0 quota 500 bytes, ++ 1.2.3.4 counter packets 9 bytes 756 quota 500 bytes used 500 bytes, + 2.2.2.2 counter packets 0 bytes 0 quota 1000 bytes } + } + +diff --git a/tests/shell/testcases/sets/dumps/0063set_catchall_0.nft b/tests/shell/testcases/sets/dumps/0063set_catchall_0.nft +index f0d42cc..faa984b 100644 +--- a/tests/shell/testcases/sets/dumps/0063set_catchall_0.nft ++++ b/tests/shell/testcases/sets/dumps/0063set_catchall_0.nft +@@ -2,13 +2,15 @@ table ip x { + set y { + type ipv4_addr + counter +- elements = { 1.1.1.1 counter packets 0 bytes 0, * counter packets 0 bytes 0 } ++ elements = { 1.1.1.1 counter packets 0 bytes 0, ++ * counter packets 0 bytes 0 } + } + + set z { + type ipv4_addr + flags interval + counter +- elements = { 1.1.1.0/24 counter packets 0 bytes 0, * counter packets 0 bytes 0 } ++ elements = { 1.1.1.0/24 counter packets 0 bytes 0, ++ * counter packets 0 bytes 0 } + } + } +diff --git a/tests/shell/testcases/sets/dumps/0064map_catchall_0.nft b/tests/shell/testcases/sets/dumps/0064map_catchall_0.nft +index 890ed2a..a1bba84 100644 +--- a/tests/shell/testcases/sets/dumps/0064map_catchall_0.nft ++++ b/tests/shell/testcases/sets/dumps/0064map_catchall_0.nft +@@ -1,13 +1,15 @@ + table ip x { + map y { + type ipv4_addr : ipv4_addr +- elements = { 10.141.0.1 : 192.168.0.2, * : 192.168.0.4 } ++ elements = { 10.141.0.1 : 192.168.0.2, ++ * : 192.168.0.4 } + } + + map z { + type ipv4_addr : ipv4_addr + flags interval +- elements = { 10.141.0.0/24 : 192.168.0.2, * : 192.168.0.3 } ++ elements = { 10.141.0.0/24 : 192.168.0.2, ++ * : 192.168.0.3 } + } + + chain y { +diff --git a/tests/shell/testcases/sets/dumps/0067nat_interval_0.nft b/tests/shell/testcases/sets/dumps/0067nat_interval_0.nft +index b6d07fc..3e1584a 100644 +--- a/tests/shell/testcases/sets/dumps/0067nat_interval_0.nft ++++ b/tests/shell/testcases/sets/dumps/0067nat_interval_0.nft +@@ -2,7 +2,8 @@ table ip nat { + map ipportmap { + type ipv4_addr : interval ipv4_addr . inet_service + flags interval +- elements = { 192.168.1.2 : 10.141.10.1-10.141.10.3 . 8888-8999, 192.168.2.0/24 : 10.141.11.5-10.141.11.20 . 8888-8999 } ++ elements = { 192.168.1.2 : 10.141.10.1-10.141.10.3 . 8888-8999, ++ 192.168.2.0/24 : 10.141.11.5-10.141.11.20 . 8888-8999 } + } + + chain prerouting { +diff --git a/tests/shell/testcases/transactions/dumps/0047set_0.nft b/tests/shell/testcases/transactions/dumps/0047set_0.nft +index 4da397b..d8e8e38 100644 +--- a/tests/shell/testcases/transactions/dumps/0047set_0.nft ++++ b/tests/shell/testcases/transactions/dumps/0047set_0.nft +@@ -2,10 +2,15 @@ table ip filter { + map group_10060 { + type ipv4_addr : classid + flags interval +- elements = { 10.1.26.2 : 1:bbf8, 10.1.26.3 : 1:c1ad, +- 10.1.26.4 : 1:b2d7, 10.1.26.5 : 1:f705, +- 10.1.26.6 : 1:b895, 10.1.26.7 : 1:ec4c, +- 10.1.26.8 : 1:de78, 10.1.26.9 : 1:b4f3, +- 10.1.26.10 : 1:dec6, 10.1.26.11 : 1:b4c0 } ++ elements = { 10.1.26.2 : 1:bbf8, ++ 10.1.26.3 : 1:c1ad, ++ 10.1.26.4 : 1:b2d7, ++ 10.1.26.5 : 1:f705, ++ 10.1.26.6 : 1:b895, ++ 10.1.26.7 : 1:ec4c, ++ 10.1.26.8 : 1:de78, ++ 10.1.26.9 : 1:b4f3, ++ 10.1.26.10 : 1:dec6, ++ 10.1.26.11 : 1:b4c0 } + } + } diff --git a/0166-netlink-fix-stack-buffer-overrun-when-emitting-range.patch b/0166-netlink-fix-stack-buffer-overrun-when-emitting-range.patch new file mode 100644 index 0000000..551a24d --- /dev/null +++ b/0166-netlink-fix-stack-buffer-overrun-when-emitting-range.patch @@ -0,0 +1,194 @@ +From 4aa22bdc2b7ea1d652ca0a1ba3628d2087703ea6 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:11:01 +0200 +Subject: [PATCH] netlink: fix stack buffer overrun when emitting ranged + expressions + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 37dfb1972cae061c09f278933af998a7c4fc2696 + +commit 37dfb1972cae061c09f278933af998a7c4fc2696 +Author: Florian Westphal +Date: Fri Mar 14 07:50:54 2025 +0100 + + netlink: fix stack buffer overrun when emitting ranged expressions + + Included bogon input generates following Sanitizer splat: + + AddressSanitizer: dynamic-stack-buffer-overflow on address 0x7... + WRITE of size 2 at 0x7fffffffcbe4 thread T0 + #0 0x0000003a68b8 in __asan_memset (src/nft+0x3a68b8) (BuildId: 3678ff51a5405c77e3e0492b9a985910efee73b8) + #1 0x0000004eb603 in __mpz_export_data src/gmputil.c:108:2 + #2 0x0000004eb603 in netlink_export_pad src/netlink.c:256:2 + #3 0x0000004eb603 in netlink_gen_range src/netlink.c:471:2 + #4 0x0000004ea250 in __netlink_gen_data src/netlink.c:523:10 + #5 0x0000004e8ee3 in alloc_nftnl_setelem src/netlink.c:205:3 + #6 0x0000004d4541 in mnl_nft_setelem_batch src/mnl.c:1816:11 + + Problem is that the range end is emitted to the buffer at the *padded* + location (rounded up to next register size), but buffer sizing is + based of the expression length, not the padded length. + + Also extend the test script: Capture stderr and if we see + AddressSanitizer warning, make it fail. + + Same bug as the one fixed in 600b84631410 ("netlink: fix stack buffer overflow with sub-reg sized prefixes"), + just in a different function. + + Apply same fix: no dynamic array + add a range check. + + Joint work with Pablo Neira Ayuso. + + Signed-off-by: Florian Westphal + +Signed-off-by: Phil Sutter +--- + src/netlink.c | 50 +++++++++++++------ + tests/shell/testcases/bogons/assert_failures | 17 ++++++- + ..._stack_buffer_overrun_in_netlink_gen_range | 6 +++ + 3 files changed, 57 insertions(+), 16 deletions(-) + create mode 100644 tests/shell/testcases/bogons/nft-f/asan_stack_buffer_overrun_in_netlink_gen_range + +diff --git a/src/netlink.c b/src/netlink.c +index 726003f..8c191f1 100644 +--- a/src/netlink.c ++++ b/src/netlink.c +@@ -315,11 +315,15 @@ static int __netlink_gen_concat_key(uint32_t flags, const struct expr *i, + static void netlink_gen_concat_key(const struct expr *expr, + struct nft_data_linearize *nld) + { +- unsigned int len = expr->len / BITS_PER_BYTE, offset = 0; +- unsigned char data[len]; ++ unsigned int len = netlink_padded_len(expr->len) / BITS_PER_BYTE; ++ unsigned char data[NFT_MAX_EXPR_LEN_BYTES]; ++ unsigned int offset = 0; + const struct expr *i; + +- memset(data, 0, len); ++ if (len > sizeof(data)) ++ BUG("Value export of %u bytes would overflow", len); ++ ++ memset(data, 0, sizeof(data)); + + list_for_each_entry(i, &expr->expressions, list) + offset += __netlink_gen_concat_key(expr->flags, i, data + offset); +@@ -359,11 +363,15 @@ static int __netlink_gen_concat_data(int end, const struct expr *i, + static void __netlink_gen_concat_expand(const struct expr *expr, + struct nft_data_linearize *nld) + { +- unsigned int len = div_round_up(expr->len, BITS_PER_BYTE) * 2, offset = 0; +- unsigned char data[len]; ++ unsigned int len = (netlink_padded_len(expr->len) / BITS_PER_BYTE) * 2; ++ unsigned char data[NFT_MAX_EXPR_LEN_BYTES]; ++ unsigned int offset = 0; + const struct expr *i; + +- memset(data, 0, len); ++ if (len > sizeof(data)) ++ BUG("Value export of %u bytes would overflow", len); ++ ++ memset(data, 0, sizeof(data)); + + list_for_each_entry(i, &expr->expressions, list) + offset += __netlink_gen_concat_data(false, i, data + offset); +@@ -378,11 +386,15 @@ static void __netlink_gen_concat_expand(const struct expr *expr, + static void __netlink_gen_concat(const struct expr *expr, + struct nft_data_linearize *nld) + { +- unsigned int len = expr->len / BITS_PER_BYTE, offset = 0; +- unsigned char data[len]; ++ unsigned int len = netlink_padded_len(expr->len) / BITS_PER_BYTE; ++ unsigned char data[NFT_MAX_EXPR_LEN_BYTES]; ++ unsigned int offset = 0; + const struct expr *i; + +- memset(data, 0, len); ++ if (len > sizeof(data)) ++ BUG("Value export of %u bytes would overflow", len); ++ ++ memset(data, 0, sizeof(data)); + + list_for_each_entry(i, &expr->expressions, list) + offset += __netlink_gen_concat_data(expr->flags, i, data + offset); +@@ -450,11 +462,14 @@ static void netlink_gen_verdict(const struct expr *expr, + static void netlink_gen_range(const struct expr *expr, + struct nft_data_linearize *nld) + { +- unsigned int len = div_round_up(expr->left->len, BITS_PER_BYTE) * 2; +- unsigned char data[len]; +- unsigned int offset = 0; ++ unsigned int len = (netlink_padded_len(expr->left->len) / BITS_PER_BYTE) * 2; ++ unsigned char data[NFT_MAX_EXPR_LEN_BYTES]; ++ unsigned int offset; + +- memset(data, 0, len); ++ if (len > sizeof(data)) ++ BUG("Value export of %u bytes would overflow", len); ++ ++ memset(data, 0, sizeof(data)); + offset = netlink_export_pad(data, expr->left->value, expr->left); + netlink_export_pad(data + offset, expr->right->value, expr->right); + memcpy(nld->value, data, len); +@@ -1196,10 +1211,15 @@ static struct expr *range_expr_reduce(struct expr *range) + static struct expr *netlink_parse_interval_elem(const struct set *set, + struct expr *expr) + { +- unsigned int len = div_round_up(expr->len, BITS_PER_BYTE); ++ unsigned int len = netlink_padded_len(expr->len) / BITS_PER_BYTE; + const struct datatype *dtype = set->data->dtype; + struct expr *range, *left, *right; +- char data[len]; ++ char data[NFT_MAX_EXPR_LEN_BYTES]; ++ ++ if (len > sizeof(data)) ++ BUG("Value export of %u bytes would overflow", len); ++ ++ memset(data, 0, sizeof(data)); + + mpz_export_data(data, expr->value, dtype->byteorder, len); + left = constant_expr_alloc(&internal_location, dtype, +diff --git a/tests/shell/testcases/bogons/assert_failures b/tests/shell/testcases/bogons/assert_failures +index 7909942..3dee63b 100755 +--- a/tests/shell/testcases/bogons/assert_failures ++++ b/tests/shell/testcases/bogons/assert_failures +@@ -1,12 +1,27 @@ + #!/bin/bash + + dir=$(dirname $0)/nft-f/ ++tmpfile=$(mktemp) ++ ++cleanup() ++{ ++ rm -f "$tmpfile" ++} ++ ++trap cleanup EXIT + + for f in $dir/*; do +- $NFT --check -f "$f" ++ echo "Check $f" ++ $NFT --check -f "$f" 2> "$tmpfile" + + if [ $? -ne 1 ]; then + echo "Bogus input file $f did not cause expected error code" 1>&2 + exit 111 + fi ++ ++ if grep AddressSanitizer "$tmpfile"; then ++ echo "Address sanitizer splat for $f" 1>&2 ++ cat "$tmpfile" ++ exit 111 ++ fi + done +diff --git a/tests/shell/testcases/bogons/nft-f/asan_stack_buffer_overrun_in_netlink_gen_range b/tests/shell/testcases/bogons/nft-f/asan_stack_buffer_overrun_in_netlink_gen_range +new file mode 100644 +index 0000000..2f7872e +--- /dev/null ++++ b/tests/shell/testcases/bogons/nft-f/asan_stack_buffer_overrun_in_netlink_gen_range +@@ -0,0 +1,6 @@ ++table ip test { ++ chain y { ++ redirect to :tcp dport map { 83 : 80/3, 84 :4 } ++ } ++} ++ diff --git a/0167-parser_bison-reject-non-serializeable-typeof-express.patch b/0167-parser_bison-reject-non-serializeable-typeof-express.patch new file mode 100644 index 0000000..8c4fb53 --- /dev/null +++ b/0167-parser_bison-reject-non-serializeable-typeof-express.patch @@ -0,0 +1,86 @@ +From 96245f9d9e6cdc61a228e3e525fac95dffec7e61 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:11:01 +0200 +Subject: [PATCH] parser_bison: reject non-serializeable typeof expressions + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit a1bb1814148c5011d50cb566a92b3b30fff118b0 + +commit a1bb1814148c5011d50cb566a92b3b30fff118b0 +Author: Florian Westphal +Date: Sun Mar 16 22:39:10 2025 +0100 + + parser_bison: reject non-serializeable typeof expressions + + Included bogon asserts with: + BUG: unhandled key type 13 + nft: src/intervals.c:73: setelem_expr_to_range: Assertion `0' failed. + + This should be rejected at parser stage, but the check for udata + support was only done on the first item in a concatenation. + + After fix, parser rejects this with: + Error: primary expression type 'symbol' lacks typeof serialization + + Fixes: 6e48df5329ea ("src: add "typeof" build/parse/print support") + Signed-off-by: Florian Westphal + +Signed-off-by: Phil Sutter +--- + src/parser_bison.y | 14 ++++++++++---- + .../nft-f/typeof_map_with_plain_integer_assert | 7 +++++++ + 2 files changed, 17 insertions(+), 4 deletions(-) + create mode 100644 tests/shell/testcases/bogons/nft-f/typeof_map_with_plain_integer_assert + +diff --git a/src/parser_bison.y b/src/parser_bison.y +index e601934..c3a5368 100644 +--- a/src/parser_bison.y ++++ b/src/parser_bison.y +@@ -816,8 +816,8 @@ int nft_lex(void *, void *, void *); + + %type symbol_expr verdict_expr integer_expr variable_expr chain_expr policy_expr + %destructor { expr_free($$); } symbol_expr verdict_expr integer_expr variable_expr chain_expr policy_expr +-%type primary_expr shift_expr and_expr typeof_expr typeof_data_expr typeof_key_expr typeof_verdict_expr +-%destructor { expr_free($$); } primary_expr shift_expr and_expr typeof_expr typeof_data_expr typeof_key_expr typeof_verdict_expr ++%type primary_expr shift_expr and_expr primary_typeof_expr typeof_expr typeof_data_expr typeof_key_expr typeof_verdict_expr ++%destructor { expr_free($$); } primary_expr shift_expr and_expr primary_typeof_expr typeof_expr typeof_data_expr typeof_key_expr typeof_verdict_expr + %type exclusive_or_expr inclusive_or_expr + %destructor { expr_free($$); } exclusive_or_expr inclusive_or_expr + %type basic_expr +@@ -2136,7 +2136,7 @@ typeof_data_expr : INTERVAL typeof_expr + } + ; + +-typeof_expr : primary_expr ++primary_typeof_expr : primary_expr + { + if (expr_ops($1)->build_udata == NULL) { + erec_queue(error(&@1, "primary expression type '%s' lacks typeof serialization", expr_ops($1)->name), +@@ -2147,7 +2147,13 @@ typeof_expr : primary_expr + + $$ = $1; + } +- | typeof_expr DOT primary_expr ++ ; ++ ++typeof_expr : primary_typeof_expr ++ { ++ $$ = $1; ++ } ++ | typeof_expr DOT primary_typeof_expr + { + struct location rhs[] = { + [1] = @2, +diff --git a/tests/shell/testcases/bogons/nft-f/typeof_map_with_plain_integer_assert b/tests/shell/testcases/bogons/nft-f/typeof_map_with_plain_integer_assert +new file mode 100644 +index 0000000..f1dc12f +--- /dev/null ++++ b/tests/shell/testcases/bogons/nft-f/typeof_map_with_plain_integer_assert +@@ -0,0 +1,7 @@ ++table ip t { ++ map m { ++ typeof ip saddr . meta mark . 0: verdict ++ flags interval ++ elements = { 127.0.0.1-127.0.0.4 . 0x00123434-0x00b00122 : accept } ++ } ++} diff --git a/0168-evaluate-don-t-allow-nat-map-with-specified-protocol.patch b/0168-evaluate-don-t-allow-nat-map-with-specified-protocol.patch new file mode 100644 index 0000000..bf0e9ee --- /dev/null +++ b/0168-evaluate-don-t-allow-nat-map-with-specified-protocol.patch @@ -0,0 +1,60 @@ +From 342c716dbdef596fb3bf29ffc2b98bed9caa23db Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:11:01 +0200 +Subject: [PATCH] evaluate: don't allow nat map with specified protocol + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 43cf4a2973ee9e3ab20edce47c6a054485707592 + +commit 43cf4a2973ee9e3ab20edce47c6a054485707592 +Author: Florian Westphal +Date: Thu Mar 20 09:39:20 2025 +0100 + + evaluate: don't allow nat map with specified protocol + + Included bogon asserts: + src/netlink_linearize.c:1305: netlink_gen_nat_stmt: Assertion `stmt->nat.proto == NULL' failed. + + The comment right above the assertion says: + nat_stmt evaluation step doesn't allow + STMT_NAT_F_CONCAT && stmt->nat.proto. + + ... except it does allow it. Disable this. + + Fixes: c68314dd4263 ("src: infer NAT mapping with concatenation from set") + Signed-off-by: Florian Westphal + Reviewed-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/evaluate.c | 4 ++++ + tests/shell/testcases/bogons/nat_map_and_protocol_assert | 5 +++++ + 2 files changed, 9 insertions(+) + create mode 100644 tests/shell/testcases/bogons/nat_map_and_protocol_assert + +diff --git a/src/evaluate.c b/src/evaluate.c +index 256ca10..81fad84 100644 +--- a/src/evaluate.c ++++ b/src/evaluate.c +@@ -3897,6 +3897,10 @@ static int stmt_evaluate_nat_map(struct eval_ctx *ctx, struct stmt *stmt) + int addr_type; + int err; + ++ if (stmt->nat.proto) ++ return stmt_binary_error(ctx, stmt, stmt->nat.proto, ++ "nat map and protocol are mutually exclusive"); ++ + if (stmt->nat.family == NFPROTO_INET) + expr_family_infer(pctx, stmt->nat.addr, &stmt->nat.family); + +diff --git a/tests/shell/testcases/bogons/nat_map_and_protocol_assert b/tests/shell/testcases/bogons/nat_map_and_protocol_assert +new file mode 100644 +index 0000000..67f2ae8 +--- /dev/null ++++ b/tests/shell/testcases/bogons/nat_map_and_protocol_assert +@@ -0,0 +1,5 @@ ++table t { ++ chain y { ++ snat to ip saddr . tcp sport map { 1.1.1.1 . 1 : 1.1.1.2 . 1 } : 6 ++ } ++} diff --git a/0169-evaluate-fix-assertion-failure-with-malformed-map-de.patch b/0169-evaluate-fix-assertion-failure-with-malformed-map-de.patch new file mode 100644 index 0000000..b207394 --- /dev/null +++ b/0169-evaluate-fix-assertion-failure-with-malformed-map-de.patch @@ -0,0 +1,65 @@ +From 6498d3cfa78662f9be30a95b699f8dfa5a6308f7 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:11:01 +0200 +Subject: [PATCH] evaluate: fix assertion failure with malformed map + definitions + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 7fa22984d7841a0feeaaeb0c2ed5d3cb637097e0 + +commit 7fa22984d7841a0feeaaeb0c2ed5d3cb637097e0 +Author: Florian Westphal +Date: Thu Mar 20 14:33:05 2025 +0100 + + evaluate: fix assertion failure with malformed map definitions + + Included bogon triggers: + nft: src/evaluate.c:2267: expr_evaluate_mapping: Assertion `set->data != NULL' failed. + + After this fix, following errors will be shown: + Error: unqualified type invalid specified in map definition. Try "typeof expression" instead of "type datatype". + map m { + ^ + map m { + ^ + Error: map has no mapping data + + Fixes: 343a51702656 ("src: store expr, not dtype to track data in sets") + Signed-off-by: Florian Westphal + Reviewed-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/evaluate.c | 5 ++++- + .../bogons/nft-f/malformed_map_expr_evaluate_mapping_assert | 6 ++++++ + 2 files changed, 10 insertions(+), 1 deletion(-) + create mode 100644 tests/shell/testcases/bogons/nft-f/malformed_map_expr_evaluate_mapping_assert + +diff --git a/src/evaluate.c b/src/evaluate.c +index 81fad84..710786f 100644 +--- a/src/evaluate.c ++++ b/src/evaluate.c +@@ -2176,7 +2176,10 @@ static int expr_evaluate_mapping(struct eval_ctx *ctx, struct expr **expr) + "Key must be a constant"); + mapping->flags |= mapping->left->flags & EXPR_F_SINGLETON; + +- assert(set->data != NULL); ++ /* This can happen for malformed map definitions */ ++ if (!set->data) ++ return set_error(ctx, set, "map has no mapping data"); ++ + if (!set_is_anonymous(set->flags) && + set->data->flags & EXPR_F_INTERVAL) + datalen = set->data->len / 2; +diff --git a/tests/shell/testcases/bogons/nft-f/malformed_map_expr_evaluate_mapping_assert b/tests/shell/testcases/bogons/nft-f/malformed_map_expr_evaluate_mapping_assert +new file mode 100644 +index 0000000..c77a9c3 +--- /dev/null ++++ b/tests/shell/testcases/bogons/nft-f/malformed_map_expr_evaluate_mapping_assert +@@ -0,0 +1,6 @@ ++table ip x { ++ map m { ++ typeof ct saddr :ct expectation ++ elements = { * : none} ++ } ++} diff --git a/0170-tests-shell-extend-coverage-for-set-element-statemen.patch b/0170-tests-shell-extend-coverage-for-set-element-statemen.patch new file mode 100644 index 0000000..0d96a7d --- /dev/null +++ b/0170-tests-shell-extend-coverage-for-set-element-statemen.patch @@ -0,0 +1,148 @@ +From a9ebfe5421e59be07d2263a86ec508e2604c3441 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:11:01 +0200 +Subject: [PATCH] tests: shell: extend coverage for set element statements + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 1f3d0b9cf9cc823c1f747650a84f1b43817d460f + +commit 1f3d0b9cf9cc823c1f747650a84f1b43817d460f +Author: Pablo Neira Ayuso +Date: Fri Mar 21 11:00:40 2025 +0100 + + tests: shell: extend coverage for set element statements + + Add a test to cover the existing set element statements. + + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + tests/shell/testcases/sets/dumps/set_stmt.nft | 62 +++++++++++++++++++ + tests/shell/testcases/sets/set_stmt | 48 ++++++++++++++ + 2 files changed, 110 insertions(+) + create mode 100644 tests/shell/testcases/sets/dumps/set_stmt.nft + create mode 100755 tests/shell/testcases/sets/set_stmt + +diff --git a/tests/shell/testcases/sets/dumps/set_stmt.nft b/tests/shell/testcases/sets/dumps/set_stmt.nft +new file mode 100644 +index 0000000..f8cf08a +--- /dev/null ++++ b/tests/shell/testcases/sets/dumps/set_stmt.nft +@@ -0,0 +1,62 @@ ++table ip x { ++ set y0 { ++ type ipv4_addr ++ counter ++ elements = { 2.2.2.0 counter packets 3 bytes 4, ++ 3.3.3.0 counter packets 1 bytes 2, ++ 5.5.5.0 counter packets 1 bytes 2, ++ 6.6.6.0 counter packets 3 bytes 4 } ++ } ++ ++ set y1 { ++ type ipv4_addr ++ limit rate 1/second burst 5 packets ++ elements = { 2.2.2.1 limit rate 5/second burst 5 packets, ++ 3.3.3.1 limit rate 1/second burst 5 packets, ++ 5.5.5.1 limit rate 1/second burst 5 packets, ++ 6.6.6.1 limit rate 5/second burst 5 packets } ++ } ++ ++ set y2 { ++ type ipv4_addr ++ ct count over 2 ++ } ++ ++ set y3 { ++ type ipv4_addr ++ last ++ elements = { 2.2.2.3 last used never, ++ 3.3.3.3 last used never, ++ 5.5.5.3 last used never, ++ 6.6.6.3 last used never } ++ } ++ ++ set y4 { ++ type ipv4_addr ++ quota over 1000 bytes ++ elements = { 2.2.2.4 quota over 30000 bytes used 1000 bytes, ++ 3.3.3.4 quota over 1000 bytes, ++ 5.5.5.4 quota over 1000 bytes, ++ 6.6.6.4 quota over 30000 bytes used 1000 bytes } ++ } ++ ++ chain y0 { ++ ip daddr @y0 ++ } ++ ++ chain y1 { ++ ip daddr @y1 ++ } ++ ++ chain y2 { ++ ip daddr @y2 ++ } ++ ++ chain y3 { ++ ip daddr @y3 ++ } ++ ++ chain y4 { ++ ip daddr @y4 ++ } ++} +diff --git a/tests/shell/testcases/sets/set_stmt b/tests/shell/testcases/sets/set_stmt +new file mode 100755 +index 0000000..0433b67 +--- /dev/null ++++ b/tests/shell/testcases/sets/set_stmt +@@ -0,0 +1,48 @@ ++#!/bin/bash ++ ++test_set_stmt() { ++ local i=$1 ++ local stmt1=$2 ++ local stmt2=$3 ++ ++ RULESET="table x { ++ set y$i { ++ type ipv4_addr ++ $stmt1 ++ elements = { 5.5.5.$i $stmt1, ++ 6.6.6.$i $stmt2 } ++ } ++ chain y$i { ++ ip daddr @y$i ++ } ++}" ++ ++ $NFT -f - <<< $RULESET ++ # should work ++ if [ $? -ne 0 ] ++ then ++ exit 1 ++ fi ++ ++ # should work ++ $NFT add element x y$i { 2.2.2.$i $stmt2 } ++ if [ $? -ne 0 ] ++ then ++ exit 1 ++ fi ++ ++ # should work ++ $NFT add element x y$i { 3.3.3.$i } ++ if [ $? -ne 0 ] ++ then ++ exit 1 ++ fi ++} ++ ++test_set_stmt "0" "counter packets 1 bytes 2" "counter packets 3 bytes 4" ++test_set_stmt "1" "limit rate 1/second" "limit rate 5/second" ++test_set_stmt "2" "ct count over 2" "ct count over 5" ++test_set_stmt "3" "last" "last" ++test_set_stmt "4" "quota over 1000 bytes" "quota over 30000 bytes used 1000 bytes" ++ ++exit 0 diff --git a/0171-parser_bison-consolidate-counter-grammar-rule-for-se.patch b/0171-parser_bison-consolidate-counter-grammar-rule-for-se.patch new file mode 100644 index 0000000..bc8611e --- /dev/null +++ b/0171-parser_bison-consolidate-counter-grammar-rule-for-se.patch @@ -0,0 +1,46 @@ +From 00aa63585bc57573f7bd3d769e2dd3fe4299338c Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:11:01 +0200 +Subject: [PATCH] parser_bison: consolidate counter grammar rule for set + elements + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit fe069b8c3be4ddfd361c2a40cd861b7a49e2ae12 + +commit fe069b8c3be4ddfd361c2a40cd861b7a49e2ae12 +Author: Pablo Neira Ayuso +Date: Thu Mar 20 10:43:42 2025 +0100 + + parser_bison: consolidate counter grammar rule for set elements + + Use existing grammar rules to parse counters to simplify parser. + + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/parser_bison.y | 11 +---------- + 1 file changed, 1 insertion(+), 10 deletions(-) + +diff --git a/src/parser_bison.y b/src/parser_bison.y +index c3a5368..feab3a5 100644 +--- a/src/parser_bison.y ++++ b/src/parser_bison.y +@@ -4547,16 +4547,7 @@ set_elem_stmt_list : set_elem_stmt + } + ; + +-set_elem_stmt : COUNTER close_scope_counter +- { +- $$ = counter_stmt_alloc(&@$); +- } +- | COUNTER PACKETS NUM BYTES NUM close_scope_counter +- { +- $$ = counter_stmt_alloc(&@$); +- $$->counter.packets = $3; +- $$->counter.bytes = $5; +- } ++set_elem_stmt : counter_stmt close_scope_counter + | LIMIT RATE limit_mode limit_rate_pkts limit_burst_pkts close_scope_limit + { + if ($5 == 0) { diff --git a/0172-parser_bison-consolidate-limit-grammar-rule-for-set-.patch b/0172-parser_bison-consolidate-limit-grammar-rule-for-set-.patch new file mode 100644 index 0000000..0980c00 --- /dev/null +++ b/0172-parser_bison-consolidate-limit-grammar-rule-for-set-.patch @@ -0,0 +1,141 @@ +From 6cedfdf861e598d132b1ef400c442cf39798c7b5 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:11:01 +0200 +Subject: [PATCH] parser_bison: consolidate limit grammar rule for set elements + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit fffda19a14f141f243c2b6def5866769056b08ea +Conflicts: Adjust to missing commit 3b92dc32e60e8 + ("src: replace struct stmt_ops by type field in struct stmt") + +commit fffda19a14f141f243c2b6def5866769056b08ea +Author: Pablo Neira Ayuso +Date: Thu Mar 20 12:43:51 2025 +0100 + + parser_bison: consolidate limit grammar rule for set elements + + Define limit_stmt_alloc and limit_args to follow similar idiom that is + used for counters. + + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/parser_bison.y | 77 ++++++++++++++++++++++------------------------ + 1 file changed, 37 insertions(+), 40 deletions(-) + +diff --git a/src/parser_bison.y b/src/parser_bison.y +index feab3a5..fd46ad6 100644 +--- a/src/parser_bison.y ++++ b/src/parser_bison.y +@@ -769,6 +769,8 @@ int nft_lex(void *, void *, void *); + %destructor { stmt_free($$); } stmt match_stmt verdict_stmt set_elem_stmt + %type counter_stmt counter_stmt_alloc stateful_stmt last_stmt + %destructor { stmt_free($$); } counter_stmt counter_stmt_alloc stateful_stmt last_stmt ++%type limit_stmt_alloc ++%destructor { stmt_free($$); } limit_stmt_alloc + %type objref_stmt objref_stmt_counter objref_stmt_limit objref_stmt_quota objref_stmt_ct objref_stmt_synproxy + %destructor { stmt_free($$); } objref_stmt objref_stmt_counter objref_stmt_limit objref_stmt_quota objref_stmt_ct objref_stmt_synproxy + +@@ -3147,7 +3149,7 @@ objref_stmt : objref_stmt_counter + ; + + stateful_stmt : counter_stmt close_scope_counter +- | limit_stmt ++ | limit_stmt close_scope_limit + | quota_stmt + | connlimit_stmt + | last_stmt close_scope_last +@@ -3431,28 +3433,45 @@ log_flag_tcp : SEQUENCE + } + ; + +-limit_stmt : LIMIT RATE limit_mode limit_rate_pkts limit_burst_pkts close_scope_limit ++limit_stmt_alloc : LIMIT RATE ++ { ++ $$ = limit_stmt_alloc(&@$); ++ } ++ ; ++ ++limit_stmt : limit_stmt_alloc limit_args ++ ; ++ ++limit_args : limit_mode limit_rate_pkts limit_burst_pkts + { +- if ($5 == 0) { +- erec_queue(error(&@5, "packet limit burst must be > 0"), ++ struct limit_stmt *limit; ++ ++ assert($0->ops->type == STMT_LIMIT); ++ ++ if ($3 == 0) { ++ erec_queue(error(&@3, "packet limit burst must be > 0"), + state->msgs); + YYERROR; + } +- $$ = limit_stmt_alloc(&@$); +- $$->limit.rate = $4.rate; +- $$->limit.unit = $4.unit; +- $$->limit.burst = $5; +- $$->limit.type = NFT_LIMIT_PKTS; +- $$->limit.flags = $3; ++ limit = &$0->limit; ++ limit->rate = $2.rate; ++ limit->unit = $2.unit; ++ limit->burst = $3; ++ limit->type = NFT_LIMIT_PKTS; ++ limit->flags = $1; + } +- | LIMIT RATE limit_mode limit_rate_bytes limit_burst_bytes close_scope_limit ++ | limit_mode limit_rate_bytes limit_burst_bytes + { +- $$ = limit_stmt_alloc(&@$); +- $$->limit.rate = $4.rate; +- $$->limit.unit = $4.unit; +- $$->limit.burst = $5; +- $$->limit.type = NFT_LIMIT_PKT_BYTES; +- $$->limit.flags = $3; ++ struct limit_stmt *limit; ++ ++ assert($0->ops->type == STMT_LIMIT); ++ ++ limit = &$0->limit; ++ limit->rate = $2.rate; ++ limit->unit = $2.unit; ++ limit->burst = $3; ++ limit->type = NFT_LIMIT_PKT_BYTES; ++ limit->flags = $1; + } + ; + +@@ -4548,29 +4567,7 @@ set_elem_stmt_list : set_elem_stmt + ; + + set_elem_stmt : counter_stmt close_scope_counter +- | LIMIT RATE limit_mode limit_rate_pkts limit_burst_pkts close_scope_limit +- { +- if ($5 == 0) { +- erec_queue(error(&@5, "limit burst must be > 0"), +- state->msgs); +- YYERROR; +- } +- $$ = limit_stmt_alloc(&@$); +- $$->limit.rate = $4.rate; +- $$->limit.unit = $4.unit; +- $$->limit.burst = $5; +- $$->limit.type = NFT_LIMIT_PKTS; +- $$->limit.flags = $3; +- } +- | LIMIT RATE limit_mode limit_rate_bytes limit_burst_bytes close_scope_limit +- { +- $$ = limit_stmt_alloc(&@$); +- $$->limit.rate = $4.rate; +- $$->limit.unit = $4.unit; +- $$->limit.burst = $5; +- $$->limit.type = NFT_LIMIT_PKT_BYTES; +- $$->limit.flags = $3; +- } ++ | limit_stmt close_scope_limit + | CT COUNT NUM close_scope_ct + { + $$ = connlimit_stmt_alloc(&@$); diff --git a/0173-parser_bison-consolidate-quota-grammar-rule-for-set-.patch b/0173-parser_bison-consolidate-quota-grammar-rule-for-set-.patch new file mode 100644 index 0000000..407f6d2 --- /dev/null +++ b/0173-parser_bison-consolidate-quota-grammar-rule-for-set-.patch @@ -0,0 +1,115 @@ +From de81c2c9b8604684132541a1eac4c9b49b5d90e3 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:11:27 +0200 +Subject: [PATCH] parser_bison: consolidate quota grammar rule for set elements + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit b09011fc3a929fec2bf42fe78adaf97f065f979b +Conflicts: Adjust to missing commit 3b92dc32e60e8 + ("src: replace struct stmt_ops by type field in struct stmt") + +commit b09011fc3a929fec2bf42fe78adaf97f065f979b +Author: Pablo Neira Ayuso +Date: Thu Mar 20 10:53:00 2025 +0100 + + parser_bison: consolidate quota grammar rule for set elements + + Define quota_stmt_alloc and quota_args to follow similar idiom that is + used for counters. + + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/parser_bison.y | 49 ++++++++++++++++++++++------------------------ + 1 file changed, 23 insertions(+), 26 deletions(-) + +diff --git a/src/parser_bison.y b/src/parser_bison.y +index fd46ad6..a141ae4 100644 +--- a/src/parser_bison.y ++++ b/src/parser_bison.y +@@ -769,8 +769,8 @@ int nft_lex(void *, void *, void *); + %destructor { stmt_free($$); } stmt match_stmt verdict_stmt set_elem_stmt + %type counter_stmt counter_stmt_alloc stateful_stmt last_stmt + %destructor { stmt_free($$); } counter_stmt counter_stmt_alloc stateful_stmt last_stmt +-%type limit_stmt_alloc +-%destructor { stmt_free($$); } limit_stmt_alloc ++%type limit_stmt_alloc quota_stmt_alloc ++%destructor { stmt_free($$); } limit_stmt_alloc quota_stmt_alloc + %type objref_stmt objref_stmt_counter objref_stmt_limit objref_stmt_quota objref_stmt_ct objref_stmt_synproxy + %destructor { stmt_free($$); } objref_stmt objref_stmt_counter objref_stmt_limit objref_stmt_quota objref_stmt_ct objref_stmt_synproxy + +@@ -3150,7 +3150,7 @@ objref_stmt : objref_stmt_counter + + stateful_stmt : counter_stmt close_scope_counter + | limit_stmt close_scope_limit +- | quota_stmt ++ | quota_stmt close_scope_quota + | connlimit_stmt + | last_stmt close_scope_last + ; +@@ -3500,21 +3500,33 @@ quota_used : /* empty */ { $$ = 0; } + } + ; + +-quota_stmt : QUOTA quota_mode NUM quota_unit quota_used close_scope_quota ++quota_stmt_alloc : QUOTA ++ { ++ $$ = quota_stmt_alloc(&@$); ++ } ++ ; ++ ++quota_stmt : quota_stmt_alloc quota_args ++ ; ++ ++quota_args : quota_mode NUM quota_unit quota_used + { + struct error_record *erec; ++ struct quota_stmt *quota; + uint64_t rate; + +- erec = data_unit_parse(&@$, $4, &rate); +- free_const($4); ++ assert($0->ops->type == STMT_QUOTA); ++ ++ erec = data_unit_parse(&@$, $3, &rate); ++ free_const($3); + if (erec != NULL) { + erec_queue(erec, state->msgs); + YYERROR; + } +- $$ = quota_stmt_alloc(&@$); +- $$->quota.bytes = $3 * rate; +- $$->quota.used = $5; +- $$->quota.flags = $2; ++ quota = &$0->quota; ++ quota->bytes = $2 * rate; ++ quota->used = $4; ++ quota->flags = $1; + } + ; + +@@ -4579,22 +4591,7 @@ set_elem_stmt : counter_stmt close_scope_counter + $$->connlimit.count = $4; + $$->connlimit.flags = NFT_CONNLIMIT_F_INV; + } +- | QUOTA quota_mode NUM quota_unit quota_used close_scope_quota +- { +- struct error_record *erec; +- uint64_t rate; +- +- erec = data_unit_parse(&@$, $4, &rate); +- free_const($4); +- if (erec != NULL) { +- erec_queue(erec, state->msgs); +- YYERROR; +- } +- $$ = quota_stmt_alloc(&@$); +- $$->quota.bytes = $3 * rate; +- $$->quota.used = $5; +- $$->quota.flags = $2; +- } ++ | quota_stmt close_scope_quota + | LAST USED NEVER close_scope_last + { + $$ = last_stmt_alloc(&@$); diff --git a/0174-parser_bison-consolidate-last-grammar-rule-for-set-e.patch b/0174-parser_bison-consolidate-last-grammar-rule-for-set-e.patch new file mode 100644 index 0000000..40ed045 --- /dev/null +++ b/0174-parser_bison-consolidate-last-grammar-rule-for-set-e.patch @@ -0,0 +1,94 @@ +From d8f4e70fb7dbf83e0ded3a4c6f1faeb4db5b042e Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:11:58 +0200 +Subject: [PATCH] parser_bison: consolidate last grammar rule for set elements + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 39599ad240d4abeeea2de95d865b1b759660e125 +Conflicts: Adjust to missing commit 3b92dc32e60e8 + ("src: replace struct stmt_ops by type field in struct stmt") + +commit 39599ad240d4abeeea2de95d865b1b759660e125 +Author: Pablo Neira Ayuso +Date: Thu Mar 20 11:28:57 2025 +0100 + + parser_bison: consolidate last grammar rule for set elements + + Define last_stmt_alloc and last_args to follow similar idiom that is + used for counters. + + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/parser_bison.y | 39 ++++++++++++++++++--------------------- + 1 file changed, 18 insertions(+), 21 deletions(-) + +diff --git a/src/parser_bison.y b/src/parser_bison.y +index a141ae4..d09c8fa 100644 +--- a/src/parser_bison.y ++++ b/src/parser_bison.y +@@ -769,8 +769,8 @@ int nft_lex(void *, void *, void *); + %destructor { stmt_free($$); } stmt match_stmt verdict_stmt set_elem_stmt + %type counter_stmt counter_stmt_alloc stateful_stmt last_stmt + %destructor { stmt_free($$); } counter_stmt counter_stmt_alloc stateful_stmt last_stmt +-%type limit_stmt_alloc quota_stmt_alloc +-%destructor { stmt_free($$); } limit_stmt_alloc quota_stmt_alloc ++%type limit_stmt_alloc quota_stmt_alloc last_stmt_alloc ++%destructor { stmt_free($$); } limit_stmt_alloc quota_stmt_alloc last_stmt_alloc + %type objref_stmt objref_stmt_counter objref_stmt_limit objref_stmt_quota objref_stmt_ct objref_stmt_synproxy + %destructor { stmt_free($$); } objref_stmt objref_stmt_counter objref_stmt_limit objref_stmt_quota objref_stmt_ct objref_stmt_synproxy + +@@ -3288,19 +3288,25 @@ counter_arg : PACKETS NUM + } + ; + +-last_stmt : LAST ++last_stmt_alloc : LAST + { + $$ = last_stmt_alloc(&@$); + } +- | LAST USED NEVER +- { +- $$ = last_stmt_alloc(&@$); +- } +- | LAST USED time_spec ++ ; ++ ++last_stmt : last_stmt_alloc ++ | last_stmt_alloc last_args ++ ; ++ ++last_args : USED NEVER ++ | USED time_spec + { +- $$ = last_stmt_alloc(&@$); +- $$->last.used = $3; +- $$->last.set = true; ++ struct last_stmt *last; ++ ++ assert($0->ops->type == STMT_LAST); ++ last = &$0->last; ++ last->used = $2; ++ last->set = true; + } + ; + +@@ -4592,16 +4598,7 @@ set_elem_stmt : counter_stmt close_scope_counter + $$->connlimit.flags = NFT_CONNLIMIT_F_INV; + } + | quota_stmt close_scope_quota +- | LAST USED NEVER close_scope_last +- { +- $$ = last_stmt_alloc(&@$); +- } +- | LAST USED time_spec close_scope_last +- { +- $$ = last_stmt_alloc(&@$); +- $$->last.used = $3; +- $$->last.set = true; +- } ++ | last_stmt close_scope_last + ; + + set_elem_expr_option : TIMEOUT time_spec diff --git a/0175-parser_bison-consolidate-connlimit-grammar-rule-for-.patch b/0175-parser_bison-consolidate-connlimit-grammar-rule-for-.patch new file mode 100644 index 0000000..902b808 --- /dev/null +++ b/0175-parser_bison-consolidate-connlimit-grammar-rule-for-.patch @@ -0,0 +1,104 @@ +From e7a57f857e654565887e0e857e8cd5cb0cb330bd Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:12:12 +0200 +Subject: [PATCH] parser_bison: consolidate connlimit grammar rule for set + elements + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 986272519136d7ae91d9bad1e6296a21bc4a3ef2 +Conflicts: Adjust to missing commit 3b92dc32e60e8 + ("src: replace struct stmt_ops by type field in struct stmt") + +commit 986272519136d7ae91d9bad1e6296a21bc4a3ef2 +Author: Pablo Neira Ayuso +Date: Thu Mar 20 12:45:54 2025 +0100 + + parser_bison: consolidate connlimit grammar rule for set elements + + Define ct_limit_stmt_alloc and ct_limit_args to follow similar idiom + that is used for counters. + + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/parser_bison.y | 41 +++++++++++++++++++++-------------------- + 1 file changed, 21 insertions(+), 20 deletions(-) + +diff --git a/src/parser_bison.y b/src/parser_bison.y +index d09c8fa..ccc942b 100644 +--- a/src/parser_bison.y ++++ b/src/parser_bison.y +@@ -769,8 +769,8 @@ int nft_lex(void *, void *, void *); + %destructor { stmt_free($$); } stmt match_stmt verdict_stmt set_elem_stmt + %type counter_stmt counter_stmt_alloc stateful_stmt last_stmt + %destructor { stmt_free($$); } counter_stmt counter_stmt_alloc stateful_stmt last_stmt +-%type limit_stmt_alloc quota_stmt_alloc last_stmt_alloc +-%destructor { stmt_free($$); } limit_stmt_alloc quota_stmt_alloc last_stmt_alloc ++%type limit_stmt_alloc quota_stmt_alloc last_stmt_alloc ct_limit_stmt_alloc ++%destructor { stmt_free($$); } limit_stmt_alloc quota_stmt_alloc last_stmt_alloc ct_limit_stmt_alloc + %type objref_stmt objref_stmt_counter objref_stmt_limit objref_stmt_quota objref_stmt_ct objref_stmt_synproxy + %destructor { stmt_free($$); } objref_stmt objref_stmt_counter objref_stmt_limit objref_stmt_quota objref_stmt_ct objref_stmt_synproxy + +@@ -3151,7 +3151,7 @@ objref_stmt : objref_stmt_counter + stateful_stmt : counter_stmt close_scope_counter + | limit_stmt close_scope_limit + | quota_stmt close_scope_quota +- | connlimit_stmt ++ | connlimit_stmt close_scope_ct + | last_stmt close_scope_last + ; + +@@ -3247,16 +3247,27 @@ verdict_map_list_member_expr: opt_newline set_elem_expr COLON verdict_expr opt_n + } + ; + +-connlimit_stmt : CT COUNT NUM close_scope_ct ++ct_limit_stmt_alloc : CT COUNT + { + $$ = connlimit_stmt_alloc(&@$); +- $$->connlimit.count = $3; + } +- | CT COUNT OVER NUM close_scope_ct ++ ; ++ ++connlimit_stmt : ct_limit_stmt_alloc ct_limit_args ++ ; ++ ++ct_limit_args : NUM + { +- $$ = connlimit_stmt_alloc(&@$); +- $$->connlimit.count = $4; +- $$->connlimit.flags = NFT_CONNLIMIT_F_INV; ++ assert($0->ops->type == STMT_CONNLIMIT); ++ ++ $0->connlimit.count = $1; ++ } ++ | OVER NUM ++ { ++ assert($0->ops->type == STMT_CONNLIMIT); ++ ++ $0->connlimit.count = $2; ++ $0->connlimit.flags = NFT_CONNLIMIT_F_INV; + } + ; + +@@ -4586,17 +4597,7 @@ set_elem_stmt_list : set_elem_stmt + + set_elem_stmt : counter_stmt close_scope_counter + | limit_stmt close_scope_limit +- | CT COUNT NUM close_scope_ct +- { +- $$ = connlimit_stmt_alloc(&@$); +- $$->connlimit.count = $3; +- } +- | CT COUNT OVER NUM close_scope_ct +- { +- $$ = connlimit_stmt_alloc(&@$); +- $$->connlimit.count = $4; +- $$->connlimit.flags = NFT_CONNLIMIT_F_INV; +- } ++ | connlimit_stmt close_scope_ct + | quota_stmt close_scope_quota + | last_stmt close_scope_last + ; diff --git a/0176-json-make-sure-timeout-list-is-initialised.patch b/0176-json-make-sure-timeout-list-is-initialised.patch new file mode 100644 index 0000000..d012a78 --- /dev/null +++ b/0176-json-make-sure-timeout-list-is-initialised.patch @@ -0,0 +1,169 @@ +From a0427ff37c71150c8afa7e57a3c96296c6dfc344 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:12:20 +0200 +Subject: [PATCH] json: make sure timeout list is initialised + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 0298bc012e020b2fca8ecc60b0091798d091e1fd + +commit 0298bc012e020b2fca8ecc60b0091798d091e1fd +Author: Florian Westphal +Date: Fri Mar 21 12:53:40 2025 +0100 + + json: make sure timeout list is initialised + + On parser error, obj_free will iterate this list. + Included json bogon crashes due to null deref because + list head initialisation did not yet happen. + + Fixes: c82a26ebf7e9 ("json: Add ct timeout support") + Signed-off-by: Florian Westphal + Reviewed-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/parser_json.c | 2 +- + tests/shell/testcases/bogons/assert_failures | 35 +++++++++--- + .../bogons/nft-j-f/ct_timeout_null_crash | 54 +++++++++++++++++++ + 3 files changed, 84 insertions(+), 7 deletions(-) + create mode 100644 tests/shell/testcases/bogons/nft-j-f/ct_timeout_null_crash + +diff --git a/src/parser_json.c b/src/parser_json.c +index ad589d7..974d87b 100644 +--- a/src/parser_json.c ++++ b/src/parser_json.c +@@ -3698,6 +3698,7 @@ static struct cmd *json_parse_cmd_add_object(struct json_ctx *ctx, + break; + case NFT_OBJECT_CT_TIMEOUT: + cmd_obj = 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)) { + if (!strcmp(tmp, "tcp")) { +@@ -3716,7 +3717,6 @@ static struct cmd *json_parse_cmd_add_object(struct json_ctx *ctx, + } + obj->ct_timeout.l3proto = l3proto; + +- init_list_head(&obj->ct_timeout.timeout_list); + if (json_parse_ct_timeout_policy(ctx, root, obj)) + goto err_free_obj; + break; +diff --git a/tests/shell/testcases/bogons/assert_failures b/tests/shell/testcases/bogons/assert_failures +index 3dee63b..74e162a 100755 +--- a/tests/shell/testcases/bogons/assert_failures ++++ b/tests/shell/testcases/bogons/assert_failures +@@ -1,6 +1,8 @@ + #!/bin/bash + + dir=$(dirname $0)/nft-f/ ++jsondir=$(dirname $0)/nft-j-f/ ++ + tmpfile=$(mktemp) + + cleanup() +@@ -10,18 +12,39 @@ cleanup() + + trap cleanup EXIT + +-for f in $dir/*; do +- echo "Check $f" +- $NFT --check -f "$f" 2> "$tmpfile" ++die_on_error() ++{ ++ local rv="$1" ++ local fname="$2" + +- if [ $? -ne 1 ]; then +- echo "Bogus input file $f did not cause expected error code" 1>&2 ++ if [ $rv -ne 1 ]; then ++ echo "Bogus input file $fname did not cause expected error code" 1>&2 + exit 111 + fi + + if grep AddressSanitizer "$tmpfile"; then +- echo "Address sanitizer splat for $f" 1>&2 ++ echo "Address sanitizer splat for $fname" 1>&2 + cat "$tmpfile" + exit 111 + fi ++} ++ ++for f in $dir/*; do ++ echo "Check $f" ++ $NFT --check -f "$f" 2> "$tmpfile" ++ ++ die_on_error $? "$f" ++done ++ ++if [ "$NFT_TEST_HAVE_json" = "n" ];then ++ # Intentionally do not skip if we lack json input, ++ # we ran all the tests that we could. ++ exit 0 ++fi ++ ++for f in $jsondir/*; do ++ echo "Check json input $f" ++ $NFT --check -j -f "$f" 2> "$tmpfile" ++ ++ die_on_error $? + done +diff --git a/tests/shell/testcases/bogons/nft-j-f/ct_timeout_null_crash b/tests/shell/testcases/bogons/nft-j-f/ct_timeout_null_crash +new file mode 100644 +index 0000000..c8c662e +--- /dev/null ++++ b/tests/shell/testcases/bogons/nft-j-f/ct_timeout_null_crash +@@ -0,0 +1,54 @@ ++{ ++ "nftables": [ ++ { ++ "metainfo": { ++ "version": "VERSION", ++ "release_name": "RELEASE_NAME", ++ "json_schema_version": 1 ++ } ++ }, ++ { ++ "table": { ++ "family": "ip", ++ "name": "filter", ++ "handle": 0 ++ } ++ }, ++ { ++ "chain": { ++ "family": "ip", ++ "table": "filter", ++ "name": "c", ++ "handle": 0 ++ } ++ }, ++ { ++ "ct timeout": { ++ "family": "ip", ++ "name": "cttime", ++ "table": "filter", ++ "handle": 0, ++ "protocol": "Xcp", ++ "l3proto": "ip", ++ "policy": { ++ "established": 123, ++ "close": 12 ++ } ++ } ++ }, ++ { ++ "rule": { ++ "family": "ip", ++ "table": "filter", ++ "chain": "c", ++ "handle": 0, ++ "expr": [ ++ { ++ "ct timeout": "cttime" ++ } ++ ] ++ } ++ } ++ ] ++} ++ diff --git a/0177-evaluate-don-t-update-cache-for-anonymous-chains.patch b/0177-evaluate-don-t-update-cache-for-anonymous-chains.patch new file mode 100644 index 0000000..01b9629 --- /dev/null +++ b/0177-evaluate-don-t-update-cache-for-anonymous-chains.patch @@ -0,0 +1,60 @@ +From b0db060d0396ebc73d2e6533b1be6fe10236f711 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:12:20 +0200 +Subject: [PATCH] evaluate: don't update cache for anonymous chains + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit bd1fc6c740535d1ad3f38f8ba9c539c14f1732f3 + +commit bd1fc6c740535d1ad3f38f8ba9c539c14f1732f3 +Author: Florian Westphal +Date: Wed Mar 19 21:05:53 2025 +0100 + + evaluate: don't update cache for anonymous chains + + Chain lookup needs a name, not a numerical id. + After patch, loading bogon gives following errors: + + Error: No symbol type information a b index 1 10.1.26.a + + v2: Don't return an error, just make it a no-op (Pablo Neira Ayuso) + + Fixes: c330152b7f77 ("src: support for implicit chain bindings") + Signed-off-by: Florian Westphal + +Signed-off-by: Phil Sutter +--- + src/evaluate.c | 4 ++++ + .../bogons/nft-f/null_deref_on_anon_chain_update_crash | 8 ++++++++ + 2 files changed, 12 insertions(+) + create mode 100644 tests/shell/testcases/bogons/nft-f/null_deref_on_anon_chain_update_crash + +diff --git a/src/evaluate.c b/src/evaluate.c +index 710786f..5a7635d 100644 +--- a/src/evaluate.c ++++ b/src/evaluate.c +@@ -5031,6 +5031,10 @@ static int rule_cache_update(struct eval_ctx *ctx, enum cmd_ops op) + if (!table) + return table_not_found(ctx); + ++ /* chain is anonymous, adding new rules via index is not supported. */ ++ if (!rule->handle.chain.name) ++ return 0; ++ + chain = chain_cache_find(table, rule->handle.chain.name); + if (!chain) + return chain_not_found(ctx); +diff --git a/tests/shell/testcases/bogons/nft-f/null_deref_on_anon_chain_update_crash b/tests/shell/testcases/bogons/nft-f/null_deref_on_anon_chain_update_crash +new file mode 100644 +index 0000000..310486c +--- /dev/null ++++ b/tests/shell/testcases/bogons/nft-f/null_deref_on_anon_chain_update_crash +@@ -0,0 +1,8 @@ ++table ip f { ++ chain c { ++ jump { ++ accept ++ } ++ } ++} ++a b index 1 10.1.26.a diff --git a/0178-tests-shell-missing-ct-count-elements-in-new-set_stm.patch b/0178-tests-shell-missing-ct-count-elements-in-new-set_stm.patch new file mode 100644 index 0000000..72a73c6 --- /dev/null +++ b/0178-tests-shell-missing-ct-count-elements-in-new-set_stm.patch @@ -0,0 +1,41 @@ +From 0ae0614d239ddefde390025bffc4807ce642a046 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:12:20 +0200 +Subject: [PATCH] tests: shell: missing ct count elements in new set_stmt test + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 74ba5ff75bf79bb6b4aa68e4bbddad47e4984beb + +commit 74ba5ff75bf79bb6b4aa68e4bbddad47e4984beb +Author: Pablo Neira Ayuso +Date: Sat Mar 22 21:43:26 2025 +0100 + + tests: shell: missing ct count elements in new set_stmt test + + Add missing entries to dump file. + + Reported-by: Florian Westphal + Fixes: 1f3d0b9cf9cc ("tests: shell: extend coverage for set element statements") + Signed-off-by: Pablo Neira Ayuso + Signed-off-by: Florian Westphal + +Signed-off-by: Phil Sutter +--- + tests/shell/testcases/sets/dumps/set_stmt.nft | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/tests/shell/testcases/sets/dumps/set_stmt.nft b/tests/shell/testcases/sets/dumps/set_stmt.nft +index f8cf08a..71ba799 100644 +--- a/tests/shell/testcases/sets/dumps/set_stmt.nft ++++ b/tests/shell/testcases/sets/dumps/set_stmt.nft +@@ -20,6 +20,10 @@ table ip x { + set y2 { + type ipv4_addr + ct count over 2 ++ elements = { 2.2.2.2 ct count over 5, ++ 3.3.3.2 ct count over 2, ++ 5.5.5.2 ct count over 2, ++ 6.6.6.2 ct count over 5 } + } + + set y3 { diff --git a/0179-optimize-compact-bitmask-matching-in-set-map.patch b/0179-optimize-compact-bitmask-matching-in-set-map.patch new file mode 100644 index 0000000..4604dd5 --- /dev/null +++ b/0179-optimize-compact-bitmask-matching-in-set-map.patch @@ -0,0 +1,180 @@ +From 3e8c6761ca0584071c7cfb2260050bae62e06487 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:12:20 +0200 +Subject: [PATCH] optimize: compact bitmask matching in set/map + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 447ac8a3e13f4706b0900d26c5c89dfcaa6773aa + +commit 447ac8a3e13f4706b0900d26c5c89dfcaa6773aa +Author: Pablo Neira Ayuso +Date: Wed Mar 26 21:54:06 2025 +0100 + + optimize: compact bitmask matching in set/map + + Check if right hand side of relational is a bitmask, ie. + + relational + / \ + ... or + / \ + value or + / \ + value value + + then, if left hand side is a binop expression, compare left and right + hand sides (not only left hand of this binop expression) to check for + redundant matches in consecutive rules, ie. + + relational + / \ + and ... + / \ + payload value + + before this patch, only payload in the binop expression was compared. + + This allows to compact several rules matching tcp flags in a set/map, eg. + + # nft -c -o -f ruleset.nft + Merging: + ruleset.nft:7:17-76: tcp flags & (fin | syn | rst | ack | urg) == fin | ack | urg + ruleset.nft:8:17-70: tcp flags & (fin | syn | rst | ack | urg) == fin | ack + ruleset.nft:9:17-64: tcp flags & (fin | syn | rst | ack | urg) == fin + ruleset.nft:10:17-70: tcp flags & (fin | syn | rst | ack | urg) == syn | ack + ruleset.nft:11:17-64: tcp flags & (fin | syn | rst | ack | urg) == syn + ruleset.nft:12:17-70: tcp flags & (fin | syn | rst | ack | urg) == rst | ack + ruleset.nft:13:17-64: tcp flags & (fin | syn | rst | ack | urg) == rst + ruleset.nft:14:17-70: tcp flags & (fin | syn | rst | ack | urg) == ack | urg + ruleset.nft:15:17-64: tcp flags & (fin | syn | rst | ack | urg) == ack + into: + tcp flags & (fin | syn | rst | ack | urg) == { fin | ack | urg, fin | ack, fin, syn | ack, syn, rst | ack, rst, ack | urg, ack } + Merging: + ruleset.nft:17:17-61: tcp flags & (ack | urg) == ack jump ack_chain + ruleset.bft:18:17-61: tcp flags & (ack | urg) == urg jump urg_chain + into: + tcp flags & (ack | urg) vmap { ack : jump ack_chain, urg : jump urg_chain } + + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/optimize.c | 35 ++++++++++++++++++- + tests/shell/testcases/optimizations/bitmask | 26 ++++++++++++++ + .../testcases/optimizations/dumps/bitmask.nft | 14 ++++++++ + 3 files changed, 74 insertions(+), 1 deletion(-) + create mode 100755 tests/shell/testcases/optimizations/bitmask + create mode 100644 tests/shell/testcases/optimizations/dumps/bitmask.nft + +diff --git a/src/optimize.c b/src/optimize.c +index 8024590..c4295f9 100644 +--- a/src/optimize.c ++++ b/src/optimize.c +@@ -125,7 +125,17 @@ static bool __expr_cmp(const struct expr *expr_a, const struct expr *expr_b) + return false; + break; + case EXPR_BINOP: +- return __expr_cmp(expr_a->left, expr_b->left); ++ if (!__expr_cmp(expr_a->left, expr_b->left)) ++ return false; ++ ++ return __expr_cmp(expr_a->right, expr_b->right); ++ case EXPR_SYMBOL: ++ if (expr_a->symtype != expr_b->symtype) ++ return false; ++ if (expr_a->symtype != SYMBOL_VALUE) ++ return false; ++ ++ return !strcmp(expr_a->identifier, expr_b->identifier); + default: + return false; + } +@@ -133,6 +143,25 @@ static bool __expr_cmp(const struct expr *expr_a, const struct expr *expr_b) + return true; + } + ++static bool is_bitmask(const struct expr *expr) ++{ ++ switch (expr->etype) { ++ case EXPR_BINOP: ++ if (expr->op == OP_OR && ++ !is_bitmask(expr->left)) ++ return false; ++ ++ return is_bitmask(expr->right); ++ case EXPR_VALUE: ++ case EXPR_SYMBOL: ++ return true; ++ default: ++ break; ++ } ++ ++ return false; ++} ++ + static bool stmt_expr_supported(const struct expr *expr) + { + switch (expr->right->etype) { +@@ -143,6 +172,10 @@ static bool stmt_expr_supported(const struct expr *expr) + case EXPR_LIST: + case EXPR_VALUE: + return true; ++ case EXPR_BINOP: ++ if (is_bitmask(expr->right)) ++ return true; ++ break; + default: + break; + } +diff --git a/tests/shell/testcases/optimizations/bitmask b/tests/shell/testcases/optimizations/bitmask +new file mode 100755 +index 0000000..064d956 +--- /dev/null ++++ b/tests/shell/testcases/optimizations/bitmask +@@ -0,0 +1,26 @@ ++#!/bin/bash ++ ++set -e ++ ++RULESET='table inet t { ++ chain ack_chain {} ++ chain urg_chain {} ++ ++ chain c { ++ tcp flags & (syn | rst | ack | urg) == ack | urg ++ tcp flags & (fin | syn | rst | ack | urg) == fin | ack | urg ++ tcp flags & (fin | syn | rst | ack | urg) == fin | ack ++ tcp flags & (fin | syn | rst | ack | urg) == fin ++ tcp flags & (fin | syn | rst | ack | urg) == syn | ack ++ tcp flags & (fin | syn | rst | ack | urg) == syn ++ tcp flags & (fin | syn | rst | ack | urg) == rst | ack ++ tcp flags & (fin | syn | rst | ack | urg) == rst ++ tcp flags & (fin | syn | rst | ack | urg) == ack | urg ++ tcp flags & (fin | syn | rst | ack | urg) == ack ++ tcp flags & (rst | ack | urg) == rst | ack ++ tcp flags & (ack | urg) == ack jump ack_chain ++ tcp flags & (ack | urg) == urg jump urg_chain ++ } ++}' ++ ++$NFT -o -f - <<< $RULESET +diff --git a/tests/shell/testcases/optimizations/dumps/bitmask.nft b/tests/shell/testcases/optimizations/dumps/bitmask.nft +new file mode 100644 +index 0000000..758b32a +--- /dev/null ++++ b/tests/shell/testcases/optimizations/dumps/bitmask.nft +@@ -0,0 +1,14 @@ ++table inet t { ++ chain ack_chain { ++ } ++ ++ chain urg_chain { ++ } ++ ++ chain c { ++ tcp flags & (syn | rst | ack | urg) == ack | urg ++ tcp flags & (fin | syn | rst | ack | urg) == { fin | ack | urg, fin | ack, fin, syn | ack, syn, rst | ack, rst, ack | urg, ack } ++ tcp flags & (rst | ack | urg) == rst | ack ++ tcp flags & (ack | urg) vmap { ack : jump ack_chain, urg : jump urg_chain } ++ } ++} diff --git a/0180-optimize-incorrect-comparison-for-reject-statement.patch b/0180-optimize-incorrect-comparison-for-reject-statement.patch new file mode 100644 index 0000000..45e8fc1 --- /dev/null +++ b/0180-optimize-incorrect-comparison-for-reject-statement.patch @@ -0,0 +1,38 @@ +From 1465dfac159747261db7f95d14833071a7ca85db Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:12:20 +0200 +Subject: [PATCH] optimize: incorrect comparison for reject statement + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit abab6e60c755aef7e1ab9d3320effa714a0b49e2 + +commit abab6e60c755aef7e1ab9d3320effa714a0b49e2 +Author: Pablo Neira Ayuso +Date: Wed Mar 26 21:54:04 2025 +0100 + + optimize: incorrect comparison for reject statement + + Logic is reverse, this should returns false if the compared reject + expressions are not the same. + + Fixes: 38d48fe57fff ("optimize: fix reject statement") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/optimize.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/optimize.c b/src/optimize.c +index c4295f9..c8082c3 100644 +--- a/src/optimize.c ++++ b/src/optimize.c +@@ -265,7 +265,7 @@ static bool __stmt_type_eq(const struct stmt *stmt_a, const struct stmt *stmt_b, + if (!stmt_a->reject.expr) + return true; + +- if (__expr_cmp(stmt_a->reject.expr, stmt_b->reject.expr)) ++ if (!__expr_cmp(stmt_a->reject.expr, stmt_b->reject.expr)) + return false; + break; + case STMT_NAT: diff --git a/0181-json-don-t-BUG-when-asked-to-list-synproxies.patch b/0181-json-don-t-BUG-when-asked-to-list-synproxies.patch new file mode 100644 index 0000000..b53acdb --- /dev/null +++ b/0181-json-don-t-BUG-when-asked-to-list-synproxies.patch @@ -0,0 +1,134 @@ +From dea13da9f065b8b95b6b6cc857c1b4961e15f551 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:12:21 +0200 +Subject: [PATCH] json: don't BUG when asked to list synproxies + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 40b0708ca6dee4829a9b6e1c2f4677ff2c206b43 + +commit 40b0708ca6dee4829a9b6e1c2f4677ff2c206b43 +Author: Florian Westphal +Date: Thu Mar 27 17:32:00 2025 +0100 + + json: don't BUG when asked to list synproxies + + "-j list synproxys" triggers a BUG(). + + Rewrite this so that all enum values are handled so the compiler can alert + us to a missing value in case there are more commands in the future. + + While at it, implement a few low-hanging fruites as well. + + Not-yet-supported cases are simply ignored. + + v2: return EOPNOTSUPP for unsupported commands (Pablo Neira Ayuso) + + Signed-off-by: Florian Westphal + Reviewed-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/evaluate.c | 6 ++++-- + src/json.c | 26 ++++++++++++++++++++++++-- + src/rule.c | 12 ++++++++++-- + 3 files changed, 38 insertions(+), 6 deletions(-) + +diff --git a/src/evaluate.c b/src/evaluate.c +index 5a7635d..43b74cc 100644 +--- a/src/evaluate.c ++++ b/src/evaluate.c +@@ -5983,7 +5983,9 @@ int cmd_evaluate(struct eval_ctx *ctx, struct cmd *cmd) + return cmd_evaluate_monitor(ctx, cmd); + case CMD_IMPORT: + return cmd_evaluate_import(ctx, cmd); +- default: +- BUG("invalid command operation %u\n", cmd->op); ++ case CMD_INVALID: ++ break; + }; ++ ++ BUG("invalid command operation %u\n", cmd->op); + } +diff --git a/src/json.c b/src/json.c +index d9226e2..1998766 100644 +--- a/src/json.c ++++ b/src/json.c +@@ -1947,7 +1947,7 @@ static json_t *generate_json_metainfo(void) + int do_command_list_json(struct netlink_ctx *ctx, struct cmd *cmd) + { + struct table *table = NULL; +- json_t *root; ++ json_t *root = NULL; + + if (cmd->handle.table.name) + table = table_cache_find(&ctx->nft->cache.table_cache, +@@ -2002,6 +2002,13 @@ int do_command_list_json(struct netlink_ctx *ctx, struct cmd *cmd) + case CMD_OBJ_CT_HELPERS: + root = do_list_obj_json(ctx, cmd, NFT_OBJECT_CT_HELPER); + break; ++ case CMD_OBJ_CT_TIMEOUT: ++ case CMD_OBJ_CT_TIMEOUTS: ++ root = do_list_obj_json(ctx, cmd, NFT_OBJECT_CT_TIMEOUT); ++ case CMD_OBJ_CT_EXPECT: ++ case CMD_OBJ_CT_EXPECTATIONS: ++ root = do_list_obj_json(ctx, cmd, NFT_OBJECT_CT_EXPECT); ++ break; + case CMD_OBJ_LIMIT: + case CMD_OBJ_LIMITS: + root = do_list_obj_json(ctx, cmd, NFT_OBJECT_LIMIT); +@@ -2010,14 +2017,29 @@ int do_command_list_json(struct netlink_ctx *ctx, struct cmd *cmd) + case CMD_OBJ_SECMARKS: + root = do_list_obj_json(ctx, cmd, NFT_OBJECT_SECMARK); + break; ++ case CMD_OBJ_SYNPROXY: ++ case CMD_OBJ_SYNPROXYS: ++ root = do_list_obj_json(ctx, cmd, NFT_OBJECT_SYNPROXY); ++ break; + case CMD_OBJ_FLOWTABLE: + root = do_list_flowtable_json(ctx, cmd, table); + break; + case CMD_OBJ_FLOWTABLES: + root = do_list_flowtables_json(ctx, cmd); + break; +- default: ++ case CMD_OBJ_HOOKS: ++ return 0; ++ case CMD_OBJ_MONITOR: ++ case CMD_OBJ_MARKUP: ++ case CMD_OBJ_SETELEMS: ++ case CMD_OBJ_RULE: ++ case CMD_OBJ_EXPR: ++ case CMD_OBJ_ELEMENTS: ++ errno = EOPNOTSUPP; ++ return -1; ++ case CMD_OBJ_INVALID: + BUG("invalid command object type %u\n", cmd->obj); ++ break; + } + + if (!json_is_array(root)) { +diff --git a/src/rule.c b/src/rule.c +index 6516454..6f247a1 100644 +--- a/src/rule.c ++++ b/src/rule.c +@@ -2420,10 +2420,18 @@ static int do_command_list(struct netlink_ctx *ctx, struct cmd *cmd) + return do_list_flowtables(ctx, cmd); + case CMD_OBJ_HOOKS: + return do_list_hooks(ctx, cmd); +- default: +- BUG("invalid command object type %u\n", cmd->obj); ++ case CMD_OBJ_MONITOR: ++ case CMD_OBJ_MARKUP: ++ case CMD_OBJ_SETELEMS: ++ case CMD_OBJ_EXPR: ++ case CMD_OBJ_ELEMENTS: ++ errno = EOPNOTSUPP; ++ return -1; ++ case CMD_OBJ_INVALID: ++ break; + } + ++ BUG("invalid command object type %u\n", cmd->obj); + return 0; + } + diff --git a/0182-evaluate-compact-STMT_F_STATEFUL-checks.patch b/0182-evaluate-compact-STMT_F_STATEFUL-checks.patch new file mode 100644 index 0000000..0cdac50 --- /dev/null +++ b/0182-evaluate-compact-STMT_F_STATEFUL-checks.patch @@ -0,0 +1,86 @@ +From b315945d7479b6512b3e58d093c5699cb842a143 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:12:21 +0200 +Subject: [PATCH] evaluate: compact STMT_F_STATEFUL checks + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 36bd6d0088bca1087aeccfe14aaa786200d755bc + +commit 36bd6d0088bca1087aeccfe14aaa786200d755bc +Author: Florian Westphal +Date: Mon Mar 31 17:23:19 2025 +0200 + + evaluate: compact STMT_F_STATEFUL checks + + We'll gain another F_STATEFUL check in a followup patch, + so lets condense the pattern into a helper to reduce copypaste. + + Signed-off-by: Florian Westphal + Reviewed-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/evaluate.c | 26 ++++++++++++++------------ + 1 file changed, 14 insertions(+), 12 deletions(-) + +diff --git a/src/evaluate.c b/src/evaluate.c +index 43b74cc..c6e1a3e 100644 +--- a/src/evaluate.c ++++ b/src/evaluate.c +@@ -3210,6 +3210,17 @@ static int stmt_evaluate_payload(struct eval_ctx *ctx, struct stmt *stmt) + return expr_evaluate(ctx, &stmt->payload.val); + } + ++static int stmt_evaluate_stateful(struct eval_ctx *ctx, struct stmt *stmt, const char *name) ++{ ++ if (stmt_evaluate(ctx, stmt) < 0) ++ return -1; ++ ++ if (!(stmt->flags & STMT_F_STATEFUL)) ++ return stmt_error(ctx, stmt, "%s statement must be stateful", name); ++ ++ return 0; ++} ++ + static int stmt_evaluate_meter(struct eval_ctx *ctx, struct stmt *stmt) + { + struct expr *key, *set, *setref; +@@ -3255,11 +3266,8 @@ static int stmt_evaluate_meter(struct eval_ctx *ctx, struct stmt *stmt) + setref->set->desc.size = stmt->meter.size; + stmt->meter.set = setref; + +- if (stmt_evaluate(ctx, stmt->meter.stmt) < 0) ++ if (stmt_evaluate_stateful(ctx, stmt->meter.stmt, "meter") < 0) + return -1; +- if (!(stmt->meter.stmt->flags & STMT_F_STATEFUL)) +- return stmt_binary_error(ctx, stmt->meter.stmt, stmt, +- "meter statement must be stateful"); + + return 0; + } +@@ -4372,11 +4380,8 @@ static int stmt_evaluate_set(struct eval_ctx *ctx, struct stmt *stmt) + return expr_error(ctx->msgs, stmt->set.key, + "Key expression comments are not supported"); + list_for_each_entry(this, &stmt->set.stmt_list, list) { +- if (stmt_evaluate(ctx, this) < 0) ++ if (stmt_evaluate_stateful(ctx, this, "set") < 0) + return -1; +- if (!(this->flags & STMT_F_STATEFUL)) +- return stmt_error(ctx, this, +- "statement must be stateful"); + } + + this_set = stmt->set.set->set; +@@ -4432,11 +4437,8 @@ static int stmt_evaluate_map(struct eval_ctx *ctx, struct stmt *stmt) + "Data expression timeouts are not supported"); + + list_for_each_entry(this, &stmt->map.stmt_list, list) { +- if (stmt_evaluate(ctx, this) < 0) ++ if (stmt_evaluate_stateful(ctx, this, "map") < 0) + return -1; +- if (!(this->flags & STMT_F_STATEFUL)) +- return stmt_error(ctx, this, +- "statement must be stateful"); + } + + return 0; diff --git a/0183-evaluate-only-allow-stateful-statements-in-set-and-m.patch b/0183-evaluate-only-allow-stateful-statements-in-set-and-m.patch new file mode 100644 index 0000000..07ad91f --- /dev/null +++ b/0183-evaluate-only-allow-stateful-statements-in-set-and-m.patch @@ -0,0 +1,93 @@ +From fc1686fdda8b31cdcdffba8f14de299613920673 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:12:21 +0200 +Subject: [PATCH] evaluate: only allow stateful statements in set and map + definitions + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 0acd81559ec9efe2cc3d869bfc8e5a0b4d888456 + +commit 0acd81559ec9efe2cc3d869bfc8e5a0b4d888456 +Author: Florian Westphal +Date: Mon Mar 31 17:23:20 2025 +0200 + + evaluate: only allow stateful statements in set and map definitions + + The bison parser doesn't allow this to happen due to grammar + restrictions, but the json input has no such issues. + + The bogon input assigns 'notrack' which triggers: + BUG: unknown stateful statement type 19 + nft: src/netlink_linearize.c:1061: netlink_gen_stmt_stateful: Assertion `0' failed. + + After patch, we get: + Error: map statement must be stateful + + Fixes: 07958ec53830 ("json: add set statement list support") + Signed-off-by: Florian Westphal + Reviewed-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/evaluate.c | 5 ++- + .../unkown_stateful_statement_type_19_assert | 34 +++++++++++++++++++ + 2 files changed, 38 insertions(+), 1 deletion(-) + create mode 100644 tests/shell/testcases/bogons/nft-j-f/unkown_stateful_statement_type_19_assert + +diff --git a/src/evaluate.c b/src/evaluate.c +index c6e1a3e..33902be 100644 +--- a/src/evaluate.c ++++ b/src/evaluate.c +@@ -4835,8 +4835,11 @@ static int set_evaluate(struct eval_ctx *ctx, struct set *set) + if (set->timeout) + set->flags |= NFT_SET_TIMEOUT; + +- list_for_each_entry(stmt, &set->stmt_list, list) ++ list_for_each_entry(stmt, &set->stmt_list, list) { ++ if (stmt_evaluate_stateful(ctx, stmt,type) < 0) ++ return -1; + num_stmts++; ++ } + + if (num_stmts > 1) + set->flags |= NFT_SET_EXPR; +diff --git a/tests/shell/testcases/bogons/nft-j-f/unkown_stateful_statement_type_19_assert b/tests/shell/testcases/bogons/nft-j-f/unkown_stateful_statement_type_19_assert +new file mode 100644 +index 0000000..e8a0f76 +--- /dev/null ++++ b/tests/shell/testcases/bogons/nft-j-f/unkown_stateful_statement_type_19_assert +@@ -0,0 +1,34 @@ ++{ ++ "nftables": [ ++ { ++ "metainfo": { ++ "version": "VERSION", ++ "release_name": "RELEASE_NAME", ++ "json_schema_version": 1 ++ } ++ }, ++ { ++ "table": { ++ "family": "ip", ++ "name": "t", ++ "handle": 0 ++ } ++ }, ++ { ++ "map": { ++ "family": "ip", ++ "name": "m", ++ "table": "t", ++ "type": "ipv4_addr", ++ "handle": 0, ++ "map": "mark", ++ "stmt": [ ++ { ++ "notrack": null ++ } ++ ] ++ } ++ } ++ ] ++} ++ diff --git a/0184-cache-don-t-crash-when-filter-is-NULL.patch b/0184-cache-don-t-crash-when-filter-is-NULL.patch new file mode 100644 index 0000000..657cb09 --- /dev/null +++ b/0184-cache-don-t-crash-when-filter-is-NULL.patch @@ -0,0 +1,60 @@ +From 70a1f2f55bd3a415103c87dd9837e847f80dacb6 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:12:21 +0200 +Subject: [PATCH] cache: don't crash when filter is NULL + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 3f0a47f9f00c9f368997c397b0848966029c39fb + +commit 3f0a47f9f00c9f368997c397b0848966029c39fb +Author: Florian Westphal +Date: Tue Apr 1 16:29:14 2025 +0200 + + cache: don't crash when filter is NULL + + a delete request will cause a crash in obj_cache_dump, move the deref + into the filter block. + + Fixes: dbff26bfba83 ("cache: consolidate reset command") + Signed-off-by: Florian Westphal + Reviewed-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/cache.c | 6 ++++-- + .../testcases/bogons/nft-f/delete_nonexistant_object_crash | 1 + + 2 files changed, 5 insertions(+), 2 deletions(-) + create mode 100644 tests/shell/testcases/bogons/nft-f/delete_nonexistant_object_crash + +diff --git a/src/cache.c b/src/cache.c +index 0f38fb2..a58c596 100644 +--- a/src/cache.c ++++ b/src/cache.c +@@ -914,6 +914,7 @@ static struct nftnl_obj_list *obj_cache_dump(struct netlink_ctx *ctx, + int family = NFPROTO_UNSPEC; + const char *table = NULL; + const char *obj = NULL; ++ bool reset = false; + bool dump = true; + + if (filter) { +@@ -926,9 +927,10 @@ static struct nftnl_obj_list *obj_cache_dump(struct netlink_ctx *ctx, + } + if (filter->list.obj_type) + type = filter->list.obj_type; ++ ++ reset = filter->reset.obj; + } +- obj_list = mnl_nft_obj_dump(ctx, family, table, obj, type, dump, +- filter->reset.obj); ++ obj_list = mnl_nft_obj_dump(ctx, family, table, obj, type, dump, reset); + if (!obj_list) { + if (errno == EINTR) + return NULL; +diff --git a/tests/shell/testcases/bogons/nft-f/delete_nonexistant_object_crash b/tests/shell/testcases/bogons/nft-f/delete_nonexistant_object_crash +new file mode 100644 +index 0000000..c369dec +--- /dev/null ++++ b/tests/shell/testcases/bogons/nft-f/delete_nonexistant_object_crash +@@ -0,0 +1 @@ ++delete quota a b diff --git a/0185-optimize-expand-expression-list-when-merging-into-co.patch b/0185-optimize-expand-expression-list-when-merging-into-co.patch new file mode 100644 index 0000000..7486386 --- /dev/null +++ b/0185-optimize-expand-expression-list-when-merging-into-co.patch @@ -0,0 +1,84 @@ +From a4724e7ed7dbee3f7a8dc5c4564891141d9e8831 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:12:21 +0200 +Subject: [PATCH] optimize: expand expression list when merging into + concatenation + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 0d17d28bb06bf2a04862d5cd879a14bcb9a2d2dc +Conflicts: Dropped changes to non-existent .json-nft dump + +commit 0d17d28bb06bf2a04862d5cd879a14bcb9a2d2dc +Author: Pablo Neira Ayuso +Date: Tue Apr 1 18:11:45 2025 +0200 + + optimize: expand expression list when merging into concatenation + + The following rules: + + udp dport 137 ct state new,untracked accept + udp dport 138 ct state new,untracked accept + + results in: + + nft: src/optimize.c:670: __merge_concat: Assertion `0' failed. + + The logic to expand to the new,untracked list in the concatenation is + missing. + + Fixes: 187c6d01d357 ("optimize: expand implicit set element when merging into concatenation") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/optimize.c | 10 ++++++++++ + .../optimizations/dumps/merge_stmts_concat.nft | 1 + + tests/shell/testcases/optimizations/merge_stmts_concat | 2 ++ + 3 files changed, 13 insertions(+) + +diff --git a/src/optimize.c b/src/optimize.c +index c8082c3..c5147d7 100644 +--- a/src/optimize.c ++++ b/src/optimize.c +@@ -665,6 +665,16 @@ static void __merge_concat(const struct optimize_ctx *ctx, uint32_t i, + clone = expr_clone(stmt_a->expr->right); + compound_expr_add(concat, clone); + break; ++ case EXPR_LIST: ++ list_for_each_entry(expr, &stmt_a->expr->right->expressions, list) { ++ concat_clone = expr_clone(concat); ++ clone = expr_clone(expr); ++ compound_expr_add(concat_clone, clone); ++ list_add_tail(&concat_clone->list, &pending_list); ++ } ++ list_del(&concat->list); ++ expr_free(concat); ++ break; + default: + assert(0); + break; +diff --git a/tests/shell/testcases/optimizations/dumps/merge_stmts_concat.nft b/tests/shell/testcases/optimizations/dumps/merge_stmts_concat.nft +index f56cea1..d00ac41 100644 +--- a/tests/shell/testcases/optimizations/dumps/merge_stmts_concat.nft ++++ b/tests/shell/testcases/optimizations/dumps/merge_stmts_concat.nft +@@ -2,6 +2,7 @@ table ip x { + chain y { + iifname . ip saddr . ip daddr { "eth1" . 1.1.1.1 . 2.2.2.3, "eth1" . 1.1.1.2 . 2.2.2.4, "eth1" . 1.1.1.2 . 2.2.3.0/24, "eth1" . 1.1.1.2 . 2.2.4.0-2.2.4.10, "eth2" . 1.1.1.3 . 2.2.2.5 } accept + ip protocol . th dport { tcp . 22, udp . 67 } ++ udp dport . ct state { 137 . new, 138 . new, 137 . untracked, 138 . untracked } accept + } + + chain c1 { +diff --git a/tests/shell/testcases/optimizations/merge_stmts_concat b/tests/shell/testcases/optimizations/merge_stmts_concat +index 9679d86..1fd1a30 100755 +--- a/tests/shell/testcases/optimizations/merge_stmts_concat ++++ b/tests/shell/testcases/optimizations/merge_stmts_concat +@@ -10,6 +10,8 @@ RULESET="table ip x { + meta iifname eth1 ip saddr 1.1.1.2 ip daddr 2.2.4.0-2.2.4.10 accept + meta iifname eth2 ip saddr 1.1.1.3 ip daddr 2.2.2.5 accept + ip protocol . th dport { tcp . 22, udp . 67 } ++ udp dport 137 ct state new,untracked accept ++ udp dport 138 ct state new,untracked accept + } + }" + diff --git a/0186-cache-prevent-possible-crash-rule-filter-is-NULL.patch b/0186-cache-prevent-possible-crash-rule-filter-is-NULL.patch new file mode 100644 index 0000000..57389f3 --- /dev/null +++ b/0186-cache-prevent-possible-crash-rule-filter-is-NULL.patch @@ -0,0 +1,53 @@ +From d2fce2f32e46ec9a15ef64fff0f3e43ed935b495 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:12:33 +0200 +Subject: [PATCH] cache: prevent possible crash rule filter is NULL + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 2412e760826f315ada984f7ee433a2077f180c8b + +commit 2412e760826f315ada984f7ee433a2077f180c8b +Author: Pablo Neira Ayuso +Date: Tue Apr 1 17:36:48 2025 +0200 + + cache: prevent possible crash rule filter is NULL + + Similar to 3f0a47f9f00c ("cache: don't crash when filter is NULL"). + + No real crash observed but it is good to tigthen this. + + Fixes: dbff26bfba83 ("cache: consolidate reset command") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/cache.c | 6 ++++-- + 1 file changed, 4 insertions(+), 2 deletions(-) + +diff --git a/src/cache.c b/src/cache.c +index a58c596..3c624df 100644 +--- a/src/cache.c ++++ b/src/cache.c +@@ -726,6 +726,7 @@ static int rule_cache_dump(struct netlink_ctx *ctx, const struct handle *h, + const char *chain = NULL; + uint64_t rule_handle = 0; + int family = h->family; ++ bool reset = false; + bool dump = true; + + if (filter) { +@@ -739,11 +740,12 @@ static int rule_cache_dump(struct netlink_ctx *ctx, const struct handle *h, + } + if (filter->list.family) + family = filter->list.family; ++ ++ reset = filter->reset.rule; + } + + rule_cache = mnl_nft_rule_dump(ctx, family, +- table, chain, rule_handle, dump, +- filter->reset.rule); ++ table, chain, rule_handle, dump, reset); + if (rule_cache == NULL) { + if (errno == EINTR) + return -1; diff --git a/0187-parser_json-allow-statement-stateful-statement-only-.patch b/0187-parser_json-allow-statement-stateful-statement-only-.patch new file mode 100644 index 0000000..a335e3d --- /dev/null +++ b/0187-parser_json-allow-statement-stateful-statement-only-.patch @@ -0,0 +1,41 @@ +From eaa4615f0ada1992dc970d4e55184dd1ff2e3dd8 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:12:34 +0200 +Subject: [PATCH] parser_json: allow statement stateful statement only in set + elements + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 85b5b051f0e64f106cee078fc6d66d0872770e79 + +commit 85b5b051f0e64f106cee078fc6d66d0872770e79 +Author: Pablo Neira Ayuso +Date: Tue Apr 1 09:49:48 2025 +0200 + + parser_json: allow statement stateful statement only in set elements + + Upfront reject of non stateful statements in set elements. + + Fixes: 07958ec53830 ("json: add set statement list support") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/parser_json.c | 5 +++++ + 1 file changed, 5 insertions(+) + +diff --git a/src/parser_json.c b/src/parser_json.c +index 974d87b..b76dbdc 100644 +--- a/src/parser_json.c ++++ b/src/parser_json.c +@@ -2384,6 +2384,11 @@ static void json_parse_set_stmt_list(struct json_ctx *ctx, + stmt_list_free(stmt_list); + return; + } ++ if (!(stmt->flags & STMT_F_STATEFUL)) { ++ stmt_free(stmt); ++ json_error(ctx, "Unsupported set statements array at index %zd failed.", index); ++ stmt_list_free(stmt_list); ++ } + list_add(&stmt->list, head); + head = &stmt->list; + } diff --git a/0188-parser_json-reject-empty-jump-goto-chain.patch b/0188-parser_json-reject-empty-jump-goto-chain.patch new file mode 100644 index 0000000..ef968ee --- /dev/null +++ b/0188-parser_json-reject-empty-jump-goto-chain.patch @@ -0,0 +1,51 @@ +From fc871acfc0278c8e0c90361cc55a94c5d1947480 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:12:34 +0200 +Subject: [PATCH] parser_json: reject empty jump/goto chain + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 9cce81701a033c4ff5e804fbf7a1149acb9e115a + +commit 9cce81701a033c4ff5e804fbf7a1149acb9e115a +Author: Pablo Neira Ayuso +Date: Mon Mar 31 17:55:45 2025 +0200 + + parser_json: reject empty jump/goto chain + + When parsing a verdict map json where element jumps to chain represented + as empty string. + + internal:0:0-0: Error: Parsing list expression item at index 0 failed. + internal:0:0-0: Error: Invalid set elem at index 0. + internal:0:0-0: Error: Invalid set elem expression. + internal:0:0-0: Error: Parsing command array at index 2 failed. + + Fixes: 586ad210368b ("libnftables: Implement JSON parser") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/parser_json.c | 10 +++++++--- + 1 file changed, 7 insertions(+), 3 deletions(-) + +diff --git a/src/parser_json.c b/src/parser_json.c +index b76dbdc..6468c3d 100644 +--- a/src/parser_json.c ++++ b/src/parser_json.c +@@ -1322,9 +1322,13 @@ static struct expr *json_parse_verdict_expr(struct json_ctx *ctx, + if (strcmp(type, verdict_tbl[i].name)) + continue; + +- if (verdict_tbl[i].need_chain && +- json_unpack_err(ctx, root, "{s:s}", "target", &chain)) +- return NULL; ++ if (verdict_tbl[i].need_chain) { ++ if (json_unpack_err(ctx, root, "{s:s}", "target", &chain)) ++ return NULL; ++ ++ if (!chain || chain[0] == '\0') ++ return NULL; ++ } + + return verdict_expr_alloc(int_loc, verdict_tbl[i].verdict, + json_alloc_chain_expr(chain)); diff --git a/0189-parser_json-bail-out-on-malformed-statement-in-set.patch b/0189-parser_json-bail-out-on-malformed-statement-in-set.patch new file mode 100644 index 0000000..4288025 --- /dev/null +++ b/0189-parser_json-bail-out-on-malformed-statement-in-set.patch @@ -0,0 +1,120 @@ +From 7032e78b5b236612e63f62512bf857c20ded93b3 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:12:34 +0200 +Subject: [PATCH] parser_json: bail out on malformed statement in set + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit cc7a7d025a60dced5f5e894aa1688014c9deef3d + +commit cc7a7d025a60dced5f5e894aa1688014c9deef3d +Author: Pablo Neira Ayuso +Date: Tue Apr 1 09:57:59 2025 +0200 + + parser_json: bail out on malformed statement in set + + Propagate error to caller so it bails out on malformed set statements. + + Fixes: 07958ec53830 ("json: add set statement list support") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/parser_json.c | 39 +++++++++++++++++++++++++++------------ + 1 file changed, 27 insertions(+), 12 deletions(-) + +diff --git a/src/parser_json.c b/src/parser_json.c +index 6468c3d..ca33c6c 100644 +--- a/src/parser_json.c ++++ b/src/parser_json.c +@@ -2365,9 +2365,9 @@ static struct stmt *json_parse_reject_stmt(struct json_ctx *ctx, + return stmt; + } + +-static void json_parse_set_stmt_list(struct json_ctx *ctx, +- struct list_head *stmt_list, +- json_t *stmt_json) ++static int json_parse_set_stmt_list(struct json_ctx *ctx, ++ struct list_head *stmt_list, ++ json_t *stmt_json) + { + struct list_head *head; + struct stmt *stmt; +@@ -2375,10 +2375,12 @@ static void json_parse_set_stmt_list(struct json_ctx *ctx, + size_t index; + + if (!stmt_json) +- return; ++ return 0; + +- if (!json_is_array(stmt_json)) ++ if (!json_is_array(stmt_json)) { + json_error(ctx, "Unexpected object type in stmt"); ++ return -1; ++ } + + head = stmt_list; + json_array_foreach(stmt_json, index, value) { +@@ -2386,16 +2388,19 @@ static void json_parse_set_stmt_list(struct json_ctx *ctx, + if (!stmt) { + json_error(ctx, "Parsing set statements array at index %zd failed.", index); + stmt_list_free(stmt_list); +- return; ++ return -1; + } + if (!(stmt->flags & STMT_F_STATEFUL)) { + stmt_free(stmt); + json_error(ctx, "Unsupported set statements array at index %zd failed.", index); + stmt_list_free(stmt_list); ++ return -1; + } + list_add(&stmt->list, head); + head = &stmt->list; + } ++ ++ return 0; + } + + static struct stmt *json_parse_set_stmt(struct json_ctx *ctx, +@@ -2440,8 +2445,11 @@ static struct stmt *json_parse_set_stmt(struct json_ctx *ctx, + stmt->set.key = expr; + stmt->set.set = expr2; + +- if (!json_unpack(value, "{s:o}", "stmt", &stmt_json)) +- json_parse_set_stmt_list(ctx, &stmt->set.stmt_list, stmt_json); ++ if (!json_unpack(value, "{s:o}", "stmt", &stmt_json) && ++ json_parse_set_stmt_list(ctx, &stmt->set.stmt_list, stmt_json) < 0) { ++ stmt_free(stmt); ++ return NULL; ++ } + + return stmt; + } +@@ -2497,8 +2505,11 @@ static struct stmt *json_parse_map_stmt(struct json_ctx *ctx, + stmt->map.data = expr_data; + stmt->map.set = expr2; + +- if (!json_unpack(value, "{s:o}", "stmt", &stmt_json)) +- json_parse_set_stmt_list(ctx, &stmt->set.stmt_list, stmt_json); ++ if (!json_unpack(value, "{s:o}", "stmt", &stmt_json) && ++ json_parse_set_stmt_list(ctx, &stmt->set.stmt_list, stmt_json) < 0) { ++ stmt_free(stmt); ++ return NULL; ++ } + + return stmt; + } +@@ -3445,8 +3456,12 @@ static struct cmd *json_parse_cmd_add_set(struct json_ctx *ctx, json_t *root, + json_unpack(root, "{s:i}", "size", &set->desc.size); + json_unpack(root, "{s:b}", "auto-merge", &set->automerge); + +- if (!json_unpack(root, "{s:o}", "stmt", &stmt_json)) +- json_parse_set_stmt_list(ctx, &set->stmt_list, stmt_json); ++ if (!json_unpack(root, "{s:o}", "stmt", &stmt_json) && ++ json_parse_set_stmt_list(ctx, &set->stmt_list, stmt_json) < 0) { ++ set_free(set); ++ handle_free(&h); ++ return NULL; ++ } + + handle_merge(&set->handle, &h); + diff --git a/0190-evaluate-bail-out-if-ct-saddr-daddr-dependency-canno.patch b/0190-evaluate-bail-out-if-ct-saddr-daddr-dependency-canno.patch new file mode 100644 index 0000000..43b404f --- /dev/null +++ b/0190-evaluate-bail-out-if-ct-saddr-daddr-dependency-canno.patch @@ -0,0 +1,53 @@ +From 09a6c561980d1d09aaf14c7a0db1a5edcc2139fa Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:12:34 +0200 +Subject: [PATCH] evaluate: bail out if ct saddr/daddr dependency cannot be + inserted + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 7f60519e356833ca007b138c00b9f5de09f21b56 + +commit 7f60519e356833ca007b138c00b9f5de09f21b56 +Author: Florian Westphal +Date: Thu Apr 3 01:09:22 2025 +0200 + + evaluate: bail out if ct saddr/daddr dependency cannot be inserted + + If we have an incomplete rule like "ct original saddr" in inet + family, this function generates an error because it can't determine the required protocol + dependency, hinting at missing ip/ip6 keyword. + + We should not go on in this case to avoid a redundant followup error: + + nft add rule inet f c ct original saddr 1.2.3.4 + Error: cannot determine ip protocol version, use "ip saddr" or "ip6 saddr" instead + add rule inet f c ct original saddr 1.2.3.4 + ^^^^^^^^^^^^^^^^^ + Error: Could not parse symbolic invalid expression + add rule inet f c ct original saddr 1.2.3.4 + + After this change only the first error is shown. + + Fixes: 2b29ea5f3c3e ("src: ct: add eval part to inject dependencies for ct saddr/daddr") + Signed-off-by: Florian Westphal + Reviewed-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/evaluate.c | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/src/evaluate.c b/src/evaluate.c +index 33902be..72dd4fe 100644 +--- a/src/evaluate.c ++++ b/src/evaluate.c +@@ -1163,7 +1163,8 @@ static int expr_evaluate_ct(struct eval_ctx *ctx, struct expr **expr) + switch (ct->ct.key) { + case NFT_CT_SRC: + case NFT_CT_DST: +- ct_gen_nh_dependency(ctx, ct); ++ if (ct_gen_nh_dependency(ctx, ct) < 0) ++ return -1; + break; + case NFT_CT_SRC_IP: + case NFT_CT_DST_IP: diff --git a/0191-optimize-invalidate-merge-in-case-of-duplicated-key-.patch b/0191-optimize-invalidate-merge-in-case-of-duplicated-key-.patch new file mode 100644 index 0000000..fcf2c8d --- /dev/null +++ b/0191-optimize-invalidate-merge-in-case-of-duplicated-key-.patch @@ -0,0 +1,168 @@ +From 538211af0641d444ab818a3ff0601ec52dc492c0 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:12:34 +0200 +Subject: [PATCH] optimize: invalidate merge in case of duplicated key in + set/map + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit ba6985a1faf98e6b1c87938695a2093cd9b58468 +Conflicts: Adjust to missing commit 3b92dc32e60e8 + ("src: replace struct stmt_ops by type field in struct stmt") + +commit ba6985a1faf98e6b1c87938695a2093cd9b58468 +Author: Pablo Neira Ayuso +Date: Wed Apr 9 11:38:17 2025 +0200 + + optimize: invalidate merge in case of duplicated key in set/map + + -o/--optimize results in EEXIST error when merging two rules that lead + to ambiguous set/map, for instance: + + table ip x { + chain v4icmp {} + chain v4icmpc {} + + chain y { + ip protocol icmp jump v4icmp + ip protocol icmp goto v4icmpc + } + } + + which is not possible because duplicated keys are not possible in + set/map. This is how it shows when running a test: + + Merging: + testcases/sets/dumps/sets_with_ifnames.nft:56:3-30: ip protocol icmp jump v4icmp + testcases/sets/dumps/sets_with_ifnames.nft:57:3-31: ip protocol icmp goto v4icmpc + into: + ip protocol vmap { icmp : jump v4icmp, icmp : goto v4icmpc } + internal:0:0-0: Error: Could not process rule: File exists + + Add a new step to compare rules that are candidate to be merged to + detect colissions in set/map keys in order to skip them in the next + final merging step. + + Add tests/shell unit to improve coverage. + + Fixes: fb298877ece2 ("src: add ruleset optimization infrastructure") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/optimize.c | 40 ++++++++++++++++++- + .../testcases/optimizations/nomerge_vmap | 40 +++++++++++++++++++ + 2 files changed, 79 insertions(+), 1 deletion(-) + create mode 100755 tests/shell/testcases/optimizations/nomerge_vmap + +diff --git a/src/optimize.c b/src/optimize.c +index c5147d7..c386e61 100644 +--- a/src/optimize.c ++++ b/src/optimize.c +@@ -136,6 +136,8 @@ static bool __expr_cmp(const struct expr *expr_a, const struct expr *expr_b) + return false; + + return !strcmp(expr_a->identifier, expr_b->identifier); ++ case EXPR_VALUE: ++ return !mpz_cmp(expr_a->value, expr_b->value); + default: + return false; + } +@@ -548,6 +550,8 @@ struct merge { + /* statements to be merged (index relative to statement matrix) */ + uint32_t stmt[MAX_STMTS]; + uint32_t num_stmts; ++ /* merge has been invalidated */ ++ bool skip; + }; + + static void merge_expr_stmts(const struct optimize_ctx *ctx, +@@ -1377,8 +1381,42 @@ static int chain_optimize(struct nft_ctx *nft, struct list_head *rules) + } + } + +- /* Step 4: Infer how to merge the candidate rules */ ++ /* Step 4: Invalidate merge in case of duplicated keys in set/map. */ ++ for (k = 0; k < num_merges; k++) { ++ uint32_t r1, r2; ++ ++ i = merge[k].rule_from; ++ ++ for (r1 = i; r1 < i + merge[k].num_rules; r1++) { ++ for (r2 = r1 + 1; r2 < i + merge[k].num_rules; r2++) { ++ bool match_same_value = true, match_seen = false; ++ ++ for (m = 0; m < ctx->num_stmts; m++) { ++ if (!ctx->stmt_matrix[r1][m]) ++ continue; ++ ++ switch (ctx->stmt_matrix[r1][m]->ops->type) { ++ case STMT_EXPRESSION: ++ match_seen = true; ++ if (!__expr_cmp(ctx->stmt_matrix[r1][m]->expr->right, ++ ctx->stmt_matrix[r2][m]->expr->right)) ++ match_same_value = false; ++ break; ++ default: ++ break; ++ } ++ } ++ if (match_seen && match_same_value) ++ merge[k].skip = true; ++ } ++ } ++ } ++ ++ /* Step 5: Infer how to merge the candidate rules */ + for (k = 0; k < num_merges; k++) { ++ if (merge[k].skip) ++ continue; ++ + i = merge[k].rule_from; + + for (m = 0; m < ctx->num_stmts; m++) { +diff --git a/tests/shell/testcases/optimizations/nomerge_vmap b/tests/shell/testcases/optimizations/nomerge_vmap +new file mode 100755 +index 0000000..36bdf28 +--- /dev/null ++++ b/tests/shell/testcases/optimizations/nomerge_vmap +@@ -0,0 +1,40 @@ ++#!/bin/bash ++ ++RULESET='table ip x { ++ chain NAME_lan-wg8 {} ++ chain NAME_mullvadgb-wg8 {} ++ chain NAME_mullvadus-wg8 {} ++ chain NAME_wan-wg8 {} ++ chain NAME_wg0-wg8 {} ++ chain NAME_wg1-wg8 {} ++ chain NAME_wg7-wg8 {} ++ ++ chain VZONE_wg8 { ++ iifname "wg8" counter return ++ iifname "eth1" counter jump NAME_lan-wg8 ++ iifname "eth1" counter return ++ iifname "eth3" counter jump NAME_mullvadgb-wg8 ++ iifname "eth3" counter return ++ iifname "eth2" counter jump NAME_mullvadus-wg8 ++ iifname "eth2" counter return ++ iifname "eth0" counter jump NAME_wan-wg8 ++ iifname "eth0" counter return ++ iifname "wg0" counter jump NAME_wg0-wg8 ++ iifname "wg0" counter return ++ iifname "wg1" counter jump NAME_wg1-wg8 ++ iifname "wg1" counter return ++ iifname "wg7" counter jump NAME_wg7-wg8 ++ iifname "wg7" counter return ++ counter drop comment "zone_wg8 default-action drop" ++ } ++ ++ chain v4icmp {} ++ chain v4icmpc {} ++ ++ chain y { ++ ip protocol icmp jump v4icmp ++ ip protocol icmp goto v4icmpc ++ } ++}' ++ ++$NFT -c -o -f - <<< "$RULESET" diff --git a/0192-parser_bison-add-selector_expr-rule-to-restrict-type.patch b/0192-parser_bison-add-selector_expr-rule-to-restrict-type.patch new file mode 100644 index 0000000..bf9d339 --- /dev/null +++ b/0192-parser_bison-add-selector_expr-rule-to-restrict-type.patch @@ -0,0 +1,160 @@ +From febbb15f6eeff25c1ac3e112f62e4a362d979fd8 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:13:02 +0200 +Subject: [PATCH] parser_bison: add selector_expr rule to restrict typeof_expr + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 8bbdcb7346788a067968e3aa62ac7e5a670b08af +Conflicts: Context change due to missing commit 058246016188c + ("src: allow to map key to nfqueue number") + +commit 8bbdcb7346788a067968e3aa62ac7e5a670b08af +Author: Pablo Neira Ayuso +Date: Thu Apr 10 23:23:58 2025 +0200 + + parser_bison: add selector_expr rule to restrict typeof_expr + + typeof_expr allows for symbol, constant and bitwise expressions, + restrict it to selector expressions. + + After this patch, input generated by fuzzer is rejected upfront: + + # nft -f test.nft + test.nft:3:53-53: Error: syntax error, unexpected number + typeof numgen inc mod 2 : ip daddr . 0 + ^ + test.nft:2:12-13: Error: set definition does not specify key + map t2 { + ^^ + test.nft:8:65-67: Error: No such file or directory + meta l4proto tcp dnat ip to numgen inc mod 2 map @t2 + ^^^ + test.nft:8:65-67: Error: No such file or directory + meta l4proto tcp dnat ip to numgen inc mod 2 map @t2 + ^^^ + + Revisit 4ab1e5e60779 ("src: allow use of 'verdict' in typeof + definitions") to handle verdict as string, later a token can be added + to the scanner and enable it via flex start conditions. + + Fixes: 14357cff40ed ("parser: add typeof keyword for declarations") + Reported-by: Florian Westphal + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/parser_bison.y | 43 +++++++++++-------- + ...valid_set_key_stmt_evaluate_nat_map_assert | 10 +++++ + 2 files changed, 36 insertions(+), 17 deletions(-) + create mode 100644 tests/shell/testcases/bogons/nft-f/invalid_set_key_stmt_evaluate_nat_map_assert + +diff --git a/src/parser_bison.y b/src/parser_bison.y +index ccc942b..47482fa 100644 +--- a/src/parser_bison.y ++++ b/src/parser_bison.y +@@ -818,8 +818,8 @@ int nft_lex(void *, void *, void *); + + %type symbol_expr verdict_expr integer_expr variable_expr chain_expr policy_expr + %destructor { expr_free($$); } symbol_expr verdict_expr integer_expr variable_expr chain_expr policy_expr +-%type primary_expr shift_expr and_expr primary_typeof_expr typeof_expr typeof_data_expr typeof_key_expr typeof_verdict_expr +-%destructor { expr_free($$); } primary_expr shift_expr and_expr primary_typeof_expr typeof_expr typeof_data_expr typeof_key_expr typeof_verdict_expr ++%type primary_expr shift_expr and_expr primary_typeof_expr typeof_expr typeof_data_expr typeof_key_expr typeof_verdict_expr selector_expr ++%destructor { expr_free($$); } primary_expr shift_expr and_expr primary_typeof_expr typeof_expr typeof_data_expr typeof_key_expr typeof_verdict_expr selector_expr + %type exclusive_or_expr inclusive_or_expr + %destructor { expr_free($$); } exclusive_or_expr inclusive_or_expr + %type basic_expr +@@ -2095,19 +2095,10 @@ subchain_block : /* empty */ { $$ = $-1; } + } + ; + +-typeof_verdict_expr : primary_expr ++typeof_verdict_expr : selector_expr + { + struct expr *e = $1; + +- if (e->etype == EXPR_SYMBOL && +- strcmp("verdict", e->identifier) == 0) { +- struct expr *v = verdict_expr_alloc(&@1, NF_ACCEPT, NULL); +- +- expr_free(e); +- v->flags &= ~EXPR_F_CONSTANT; +- e = v; +- } +- + if (expr_ops(e)->build_udata == NULL) { + erec_queue(error(&@1, "map data type '%s' lacks typeof serialization", expr_ops(e)->name), + state->msgs); +@@ -2116,7 +2107,7 @@ typeof_verdict_expr : primary_expr + } + $$ = e; + } +- | typeof_expr DOT primary_expr ++ | typeof_expr DOT selector_expr + { + struct location rhs[] = { + [1] = @2, +@@ -2136,9 +2127,24 @@ typeof_data_expr : INTERVAL typeof_expr + { + $$ = $1; + } ++ | STRING ++ { ++ struct expr *verdict; ++ ++ if (strcmp("verdict", $1) != 0) { ++ erec_queue(error(&@1, "map data type '%s' lacks typeof serialization", $1), ++ state->msgs); ++ free_const($1); ++ YYERROR; ++ } ++ verdict = verdict_expr_alloc(&@1, NF_ACCEPT, NULL); ++ verdict->flags &= ~EXPR_F_CONSTANT; ++ $$ = verdict; ++ free_const($1); ++ } + ; + +-primary_typeof_expr : primary_expr ++primary_typeof_expr : selector_expr + { + if (expr_ops($1)->build_udata == NULL) { + erec_queue(error(&@1, "primary expression type '%s' lacks typeof serialization", expr_ops($1)->name), +@@ -4313,9 +4319,7 @@ integer_expr : NUM + } + ; + +-primary_expr : symbol_expr { $$ = $1; } +- | integer_expr { $$ = $1; } +- | payload_expr { $$ = $1; } ++selector_expr : payload_expr { $$ = $1; } + | exthdr_expr { $$ = $1; } + | exthdr_exists_expr { $$ = $1; } + | meta_expr { $$ = $1; } +@@ -4327,6 +4331,11 @@ primary_expr : symbol_expr { $$ = $1; } + | fib_expr { $$ = $1; } + | osf_expr { $$ = $1; } + | xfrm_expr { $$ = $1; } ++ ; ++ ++primary_expr : symbol_expr { $$ = $1; } ++ | integer_expr { $$ = $1; } ++ | selector_expr { $$ = $1; } + | '(' basic_expr ')' { $$ = $2; } + ; + +diff --git a/tests/shell/testcases/bogons/nft-f/invalid_set_key_stmt_evaluate_nat_map_assert b/tests/shell/testcases/bogons/nft-f/invalid_set_key_stmt_evaluate_nat_map_assert +new file mode 100644 +index 0000000..d73dce8 +--- /dev/null ++++ b/tests/shell/testcases/bogons/nft-f/invalid_set_key_stmt_evaluate_nat_map_assert +@@ -0,0 +1,10 @@ ++table ip t { ++ map t2 { ++ typeof numgen inc mod 2 : ip daddr . 0 ++ } ++ ++ chain c { ++ type nat hook prerouting priority dstnat; policy accept; ++ meta l4proto tcp dnat ip to numgen inc mod 2 map @t2 ++ } ++} diff --git a/0193-netlink-bogus-concatenated-set-ranges-with-netlink-m.patch b/0193-netlink-bogus-concatenated-set-ranges-with-netlink-m.patch new file mode 100644 index 0000000..c6919c5 --- /dev/null +++ b/0193-netlink-bogus-concatenated-set-ranges-with-netlink-m.patch @@ -0,0 +1,864 @@ +From dcd72ff1425c2bb2089a69a6c75ef07fa9213b89 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:13:20 +0200 +Subject: [PATCH] netlink: bogus concatenated set ranges with netlink message + overrun + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 2fbade3cd9900fe7f87ac660b6ac44544e238206 + +commit 2fbade3cd9900fe7f87ac660b6ac44544e238206 +Author: Pablo Neira Ayuso +Date: Thu Apr 17 21:40:23 2025 +0200 + + netlink: bogus concatenated set ranges with netlink message overrun + + When building each component of the set element key, a late byteorder + switch is performed to ensure that all components in the interval are + represented in big endian, as required by the pipapo backend. + + In case that the set element does not fit into the netlink message, the + byteorder switch happens twice, leading to inserting an element with a + bogus component with large sets, so instead: + + "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890" + + listing reports: + + 16777216 . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890" + + Note that 16777216 is 0x1000000, which should instead be 0x00000001 to + represent "lo" as u32. + + Fix this by switching the value in a temporary variable and use it to + set the set element key attribute in the netlink message. + + Later, revisit this to perform this byteorder switch from evaluation + step. + + Add tests/shell unit to cover for this bug. + + Closes: https://bugzilla.netfilter.org/show_bug.cgi?id=1792 + Fixes: 8ac2f3b2fca3 ("src: Add support for concatenated set ranges") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/netlink.c | 19 +- + .../shell/testcases/sets/concat_nlmsg_overrun | 734 ++++++++++++++++++ + .../sets/dumps/concat_nlmsg_overrun.nft | 7 + + 3 files changed, 755 insertions(+), 5 deletions(-) + create mode 100755 tests/shell/testcases/sets/concat_nlmsg_overrun + create mode 100644 tests/shell/testcases/sets/dumps/concat_nlmsg_overrun.nft + +diff --git a/src/netlink.c b/src/netlink.c +index 8c191f1..1007571 100644 +--- a/src/netlink.c ++++ b/src/netlink.c +@@ -263,6 +263,8 @@ static int __netlink_gen_concat_key(uint32_t flags, const struct expr *i, + unsigned char *data) + { + struct expr *expr; ++ mpz_t value; ++ int ret; + + switch (i->etype) { + case EXPR_RANGE: +@@ -271,9 +273,11 @@ static int __netlink_gen_concat_key(uint32_t flags, const struct expr *i, + else + expr = i->left; + ++ mpz_init_set(value, expr->value); ++ + if (expr_basetype(expr)->type == TYPE_INTEGER && + expr->byteorder == BYTEORDER_HOST_ENDIAN) +- byteorder_switch_expr_value(expr->value, expr); ++ byteorder_switch_expr_value(value, expr); + + i = expr; + break; +@@ -294,22 +298,27 @@ static int __netlink_gen_concat_key(uint32_t flags, const struct expr *i, + } + return netlink_export_pad(data, i->prefix->value, i); + case EXPR_VALUE: +- /* Switch byteorder only once for singleton values when the set ++ mpz_init_set(value, i->value); ++ ++ /* Switch byteorder to big endian representation when the set + * contains concatenation of intervals. + */ +- if (!(flags & EXPR_F_INTERVAL)) ++ if (!(flags & (EXPR_F_INTERVAL| EXPR_F_INTERVAL_END))) + break; + + expr = (struct expr *)i; + if (expr_basetype(expr)->type == TYPE_INTEGER && + expr->byteorder == BYTEORDER_HOST_ENDIAN) +- byteorder_switch_expr_value(expr->value, expr); ++ byteorder_switch_expr_value(value, expr); + break; + default: + BUG("invalid expression type '%s' in set", expr_ops(i)->name); + } + +- return netlink_export_pad(data, i->value, i); ++ ret = netlink_export_pad(data, value, i); ++ mpz_clear(value); ++ ++ return ret; + } + + static void netlink_gen_concat_key(const struct expr *expr, +diff --git a/tests/shell/testcases/sets/concat_nlmsg_overrun b/tests/shell/testcases/sets/concat_nlmsg_overrun +new file mode 100755 +index 0000000..69cefe9 +--- /dev/null ++++ b/tests/shell/testcases/sets/concat_nlmsg_overrun +@@ -0,0 +1,734 @@ ++#!/bin/bash ++ ++# NFT_TEST_REQUIRES(NFT_TEST_HAVE_pipapo) ++ ++set -e ++ ++RULESET='flush ruleset ++ ++table ip filter { ++ set test_set { ++ type iface_index . ether_addr . ipv4_addr ++ flags interval ++ elements = { ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890", ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3, ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3, ++ "lo" . 00:11:22:33:44:55 . 10.1.2.3, ++ } ++ } ++}' ++ ++$NFT -f - <<< $RULESET ++ ++exit 0 +diff --git a/tests/shell/testcases/sets/dumps/concat_nlmsg_overrun.nft b/tests/shell/testcases/sets/dumps/concat_nlmsg_overrun.nft +new file mode 100644 +index 0000000..01d76b9 +--- /dev/null ++++ b/tests/shell/testcases/sets/dumps/concat_nlmsg_overrun.nft +@@ -0,0 +1,7 @@ ++table ip filter { ++ set test_set { ++ type iface_index . ether_addr . ipv4_addr ++ flags interval ++ elements = { "lo" . 00:11:22:33:44:55 . 10.1.2.3 comment "123456789012345678901234567890" } ++ } ++} diff --git a/0194-doc-Fix-typo-in-nat-statement-prefix-description.patch b/0194-doc-Fix-typo-in-nat-statement-prefix-description.patch new file mode 100644 index 0000000..f2d1b5d --- /dev/null +++ b/0194-doc-Fix-typo-in-nat-statement-prefix-description.patch @@ -0,0 +1,37 @@ +From 81c1ea7dd7b429b7c4215cb762c1ca075400be10 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:13:20 +0200 +Subject: [PATCH] doc: Fix typo in nat statement 'prefix' description + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 1b3c33a7931967fb7afe4efdf68deb7781786489 + +commit 1b3c33a7931967fb7afe4efdf68deb7781786489 +Author: Phil Sutter +Date: Thu May 8 16:35:47 2025 +0200 + + doc: Fix typo in nat statement 'prefix' description + + No point in repeating 'to map' here. + + Fixes: 19d73ccdd39fa ("doc: add nat examples") + Signed-off-by: Phil Sutter + +Signed-off-by: Phil Sutter +--- + doc/statements.txt | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/doc/statements.txt b/doc/statements.txt +index 834adb2..fbd01af 100644 +--- a/doc/statements.txt ++++ b/doc/statements.txt +@@ -428,7 +428,7 @@ Before kernel 4.18 nat statements require both prerouting and postrouting base c + to be present since otherwise packets on the return path won't be seen by + netfilter and therefore no reverse translation will take place. + +-The optional *prefix* keyword allows to map to map *n* source addresses to *n* ++The optional *prefix* keyword allows to map *n* source addresses to *n* + destination addresses. See 'Advanced NAT examples' below. + + .NAT statement values diff --git a/0195-parser_json-Introduce-parse_flags_array.patch b/0195-parser_json-Introduce-parse_flags_array.patch new file mode 100644 index 0000000..0451689 --- /dev/null +++ b/0195-parser_json-Introduce-parse_flags_array.patch @@ -0,0 +1,769 @@ +From f3cf701e78370d70683f7421a9e42b2da1f0a84d Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:13:20 +0200 +Subject: [PATCH] parser_json: Introduce parse_flags_array() + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 3550dd69632f3a883ab6593daeffb514c67dfb8c + +commit 3550dd69632f3a883ab6593daeffb514c67dfb8c +Author: Phil Sutter +Date: Thu Apr 10 16:42:42 2025 +0200 + + parser_json: Introduce parse_flags_array() + + Various objects support a 'flags' property with value usually being an + array of strings. There is a special case, when merely a single flag is + set: The value may be a string representing this flag. + + Introduce a function assisting in parsing this polymorphic value. Have + callers pass a parser callback translating a single flag name into a + corresponding value. Luckily, these single flag parsers are very common + already. + + As a side-effect, enable the single flag spec for set flags as well and + update the documentation accordingly. + + Signed-off-by: Phil Sutter + +Signed-off-by: Phil Sutter +--- + doc/libnftables-json.adoc | 5 +- + src/parser_json.c | 466 +++++++++++--------------------------- + 2 files changed, 136 insertions(+), 335 deletions(-) + +diff --git a/doc/libnftables-json.adoc b/doc/libnftables-json.adoc +index b90a1fc..ab4223d 100644 +--- a/doc/libnftables-json.adoc ++++ b/doc/libnftables-json.adoc +@@ -317,7 +317,7 @@ ____ + "handle":* 'NUMBER'*, + "type":* 'SET_TYPE'*, + "policy":* 'SET_POLICY'*, +- "flags": [* 'SET_FLAG_LIST' *], ++ "flags":* 'SET_FLAGS'*, + "elem":* 'SET_ELEMENTS'*, + "timeout":* 'NUMBER'*, + "gc-interval":* 'NUMBER'*, +@@ -333,7 +333,7 @@ ____ + "type":* 'SET_TYPE'*, + "map":* 'STRING'*, + "policy":* 'SET_POLICY'*, +- "flags": [* 'SET_FLAG_LIST' *], ++ "flags":* 'SET_FLAGS'*, + "elem":* 'SET_ELEMENTS'*, + "timeout":* 'NUMBER'*, + "gc-interval":* 'NUMBER'*, +@@ -344,6 +344,7 @@ ____ + 'SET_TYPE' := 'STRING' | *[* 'SET_TYPE_LIST' *]* | *{ "typeof":* 'EXPRESSION' *}* + 'SET_TYPE_LIST' := 'STRING' [*,* 'SET_TYPE_LIST' ] + 'SET_POLICY' := *"performance"* | *"memory"* ++'SET_FLAGS' := 'SET_FLAG' | *[* 'SET_FLAG_LIST' *]* + 'SET_FLAG_LIST' := 'SET_FLAG' [*,* 'SET_FLAG_LIST' ] + 'SET_FLAG' := *"constant"* | *"interval"* | *"timeout"* + 'SET_ELEMENTS' := 'EXPRESSION' | *[* 'EXPRESSION_LIST' *]* +diff --git a/src/parser_json.c b/src/parser_json.c +index ca33c6c..dc038f5 100644 +--- a/src/parser_json.c ++++ b/src/parser_json.c +@@ -198,6 +198,60 @@ static int json_unpack_stmt(struct json_ctx *ctx, json_t *root, + return 1; + } + ++/** ++ * parse_flags_array - parse JSON property as an array of flags ++ * ++ * @ctx: JSON parser context ++ * @obj: JSON object to extract property from ++ * @key: name of property containing the flags array ++ * @flag_parser: Callback parsing a single flag, returns 0 on error ++ * ++ * The property value may be a string representing a single flag or an array of ++ * strings representing a number of flags whose values are ORed together. ++ * ++ * @return: Combined flag value, 0 if no such property found or -1 if data is ++ * malformed or flag parsing failed. ++ */ ++static int parse_flags_array(struct json_ctx *ctx, json_t *obj, const char *key, ++ unsigned int (*flag_parser)(const char *flag)) ++{ ++ json_t *value = json_object_get(obj, key), *tmp; ++ size_t index; ++ int ret = 0; ++ ++ if (!value) ++ return 0; ++ ++ if (json_is_string(value)) { ++ ret = flag_parser(json_string_value(value)); ++ return ret ?: -1; ++ } ++ ++ if (!json_is_array(value)) { ++ json_error(ctx, ++ "Expecting string or array in '%s' property.", key); ++ return -1; ++ } ++ ++ json_array_foreach(value, index, tmp) { ++ int flag = 0; ++ ++ if (json_is_string(tmp)) ++ flag = flag_parser(json_string_value(tmp)); ++ ++ if (!flag) { ++ json_error(ctx, ++ "Invalid flag in '%s' property array at index %zu.", ++ key, index); ++ return -1; ++ } ++ ++ ret |= flag; ++ } ++ ++ return ret; ++} ++ + static int parse_family(const char *name, uint32_t *family) + { + unsigned int i; +@@ -1065,7 +1119,7 @@ static struct expr *json_parse_hash_expr(struct json_ctx *ctx, + return hash_expr; + } + +-static int fib_flag_parse(const char *name, int *flags) ++static unsigned int fib_flag_parse(const char *name) + { + const char *fib_flags[] = { + "saddr", +@@ -1077,12 +1131,10 @@ static int fib_flag_parse(const char *name, int *flags) + unsigned int i; + + for (i = 0; i < array_size(fib_flags); i++) { +- if (!strcmp(name, fib_flags[i])) { +- *flags |= (1 << i); +- return 0; +- } ++ if (!strcmp(name, fib_flags[i])) ++ return 1 << i; + } +- return 1; ++ return 0; + } + + static struct expr *json_parse_fib_expr(struct json_ctx *ctx, +@@ -1095,11 +1147,9 @@ static struct expr *json_parse_fib_expr(struct json_ctx *ctx, + [NFT_FIB_RESULT_ADDRTYPE] = "type", + }; + enum nft_fib_result resultval = NFT_FIB_RESULT_UNSPEC; +- json_t *flags, *value; + const char *result; +- unsigned int i; +- size_t index; + int flagval = 0; ++ unsigned int i; + + if (json_unpack_err(ctx, root, "{s:s}", "result", &result)) + return NULL; +@@ -1115,34 +1165,9 @@ static struct expr *json_parse_fib_expr(struct json_ctx *ctx, + return NULL; + } + +- if (!json_unpack(root, "{s:o}", "flags", &flags)) { +- const char *flag; +- +- if (json_is_string(flags)) { +- flag = json_string_value(flags); +- +- if (fib_flag_parse(flag, &flagval)) { +- json_error(ctx, "Invalid fib flag '%s'.", flag); +- return NULL; +- } +- } else if (!json_is_array(flags)) { +- json_error(ctx, "Unexpected object type in fib tuple."); +- return NULL; +- } +- +- json_array_foreach(flags, index, value) { +- if (!json_is_string(value)) { +- json_error(ctx, "Unexpected object type in fib flags array at index %zd.", index); +- return NULL; +- } +- flag = json_string_value(value); +- +- if (fib_flag_parse(flag, &flagval)) { +- json_error(ctx, "Invalid fib flag '%s'.", flag); +- return NULL; +- } +- } +- } ++ flagval = parse_flags_array(ctx, root, "flags", fib_flag_parse); ++ if (flagval < 0) ++ return NULL; + + /* sanity checks from fib_expr in parser_bison.y */ + +@@ -2102,8 +2127,7 @@ static struct stmt *json_parse_secmark_stmt(struct json_ctx *ctx, + return stmt; + } + +-static int json_parse_nat_flag(struct json_ctx *ctx, +- json_t *root, int *flags) ++static unsigned int json_parse_nat_flag(const char *flag) + { + const struct { + const char *flag; +@@ -2114,51 +2138,16 @@ static int json_parse_nat_flag(struct json_ctx *ctx, + { "persistent", NF_NAT_RANGE_PERSISTENT }, + { "netmap", NF_NAT_RANGE_NETMAP }, + }; +- const char *flag; + unsigned int i; + +- assert(flags); +- +- if (!json_is_string(root)) { +- json_error(ctx, "Invalid nat flag type %s, expected string.", +- json_typename(root)); +- return 1; +- } +- flag = json_string_value(root); + for (i = 0; i < array_size(flag_tbl); i++) { +- if (!strcmp(flag, flag_tbl[i].flag)) { +- *flags |= flag_tbl[i].val; +- return 0; +- } +- } +- json_error(ctx, "Unknown nat flag '%s'.", flag); +- return 1; +-} +- +-static int json_parse_nat_flags(struct json_ctx *ctx, json_t *root) +-{ +- int flags = 0; +- json_t *value; +- size_t index; +- +- if (json_is_string(root)) { +- json_parse_nat_flag(ctx, root, &flags); +- return flags; +- } else if (!json_is_array(root)) { +- json_error(ctx, "Invalid nat flags type %s.", +- json_typename(root)); +- return -1; +- } +- json_array_foreach(root, index, value) { +- if (json_parse_nat_flag(ctx, value, &flags)) +- json_error(ctx, "Parsing nat flag at index %zu failed.", +- index); ++ if (!strcmp(flag, flag_tbl[i].flag)) ++ return flag_tbl[i].val; + } +- return flags; ++ return 0; + } + +-static int json_parse_nat_type_flag(struct json_ctx *ctx, +- json_t *root, int *flags) ++static unsigned int json_parse_nat_type_flag(const char *flag) + { + const struct { + const char *flag; +@@ -2168,47 +2157,13 @@ static int json_parse_nat_type_flag(struct json_ctx *ctx, + { "prefix", STMT_NAT_F_PREFIX }, + { "concat", STMT_NAT_F_CONCAT }, + }; +- const char *flag; + unsigned int i; + +- assert(flags); +- +- if (!json_is_string(root)) { +- json_error(ctx, "Invalid nat type flag type %s, expected string.", +- json_typename(root)); +- return 1; +- } +- flag = json_string_value(root); + for (i = 0; i < array_size(flag_tbl); i++) { +- if (!strcmp(flag, flag_tbl[i].flag)) { +- *flags |= flag_tbl[i].val; +- return 0; +- } +- } +- json_error(ctx, "Unknown nat type flag '%s'.", flag); +- return 1; +-} +- +-static int json_parse_nat_type_flags(struct json_ctx *ctx, json_t *root) +-{ +- int flags = 0; +- json_t *value; +- size_t index; +- +- if (json_is_string(root)) { +- json_parse_nat_type_flag(ctx, root, &flags); +- return flags; +- } else if (!json_is_array(root)) { +- json_error(ctx, "Invalid nat flags type %s.", +- json_typename(root)); +- return -1; +- } +- json_array_foreach(root, index, value) { +- if (json_parse_nat_type_flag(ctx, value, &flags)) +- json_error(ctx, "Parsing nat type flag at index %zu failed.", +- index); ++ if (!strcmp(flag, flag_tbl[i].flag)) ++ return flag_tbl[i].val; + } +- return flags; ++ return 0; + } + + static int nat_type_parse(const char *type) +@@ -2231,7 +2186,7 @@ static int nat_type_parse(const char *type) + static struct stmt *json_parse_nat_stmt(struct json_ctx *ctx, + const char *key, json_t *value) + { +- int type, familyval; ++ int type, familyval, flags; + struct stmt *stmt; + json_t *tmp; + +@@ -2264,24 +2219,20 @@ static struct stmt *json_parse_nat_stmt(struct json_ctx *ctx, + return NULL; + } + } +- if (!json_unpack(value, "{s:o}", "flags", &tmp)) { +- int flags = json_parse_nat_flags(ctx, tmp); +- +- if (flags < 0) { +- stmt_free(stmt); +- return NULL; +- } +- stmt->nat.flags = flags; ++ flags = parse_flags_array(ctx, value, "flags", json_parse_nat_flag); ++ if (flags < 0) { ++ stmt_free(stmt); ++ return NULL; + } +- if (!json_unpack(value, "{s:o}", "type_flags", &tmp)) { +- int flags = json_parse_nat_type_flags(ctx, tmp); ++ stmt->nat.flags = flags; + +- if (flags < 0) { +- stmt_free(stmt); +- return NULL; +- } +- stmt->nat.type_flags = flags; ++ flags = parse_flags_array(ctx, value, "type_flags", ++ json_parse_nat_type_flag); ++ if (flags < 0) { ++ stmt_free(stmt); ++ return NULL; + } ++ stmt->nat.type_flags = flags; + + return stmt; + } +@@ -2514,8 +2465,7 @@ static struct stmt *json_parse_map_stmt(struct json_ctx *ctx, + return stmt; + } + +-static int json_parse_log_flag(struct json_ctx *ctx, +- json_t *root, int *flags) ++static unsigned int json_parse_log_flag(const char *flag) + { + const struct { + const char *flag; +@@ -2528,47 +2478,13 @@ static int json_parse_log_flag(struct json_ctx *ctx, + { "ether", NF_LOG_MACDECODE }, + { "all", NF_LOG_MASK }, + }; +- const char *flag; + unsigned int i; + +- assert(flags); +- +- if (!json_is_string(root)) { +- json_error(ctx, "Invalid log flag type %s, expected string.", +- json_typename(root)); +- return 1; +- } +- flag = json_string_value(root); + for (i = 0; i < array_size(flag_tbl); i++) { +- if (!strcmp(flag, flag_tbl[i].flag)) { +- *flags |= flag_tbl[i].val; +- return 0; +- } +- } +- json_error(ctx, "Unknown log flag '%s'.", flag); +- return 1; +-} +- +-static int json_parse_log_flags(struct json_ctx *ctx, json_t *root) +-{ +- int flags = 0; +- json_t *value; +- size_t index; +- +- if (json_is_string(root)) { +- json_parse_log_flag(ctx, root, &flags); +- return flags; +- } else if (!json_is_array(root)) { +- json_error(ctx, "Invalid log flags type %s.", +- json_typename(root)); +- return -1; +- } +- json_array_foreach(root, index, value) { +- if (json_parse_log_flag(ctx, value, &flags)) +- json_error(ctx, "Parsing log flag at index %zu failed.", +- index); ++ if (!strcmp(flag, flag_tbl[i].flag)) ++ return flag_tbl[i].val; + } +- return flags; ++ return 0; + } + + static struct stmt *json_parse_log_stmt(struct json_ctx *ctx, +@@ -2576,8 +2492,7 @@ static struct stmt *json_parse_log_stmt(struct json_ctx *ctx, + { + const char *tmpstr; + struct stmt *stmt; +- json_t *jflags; +- int tmp; ++ int tmp, flags; + + stmt = log_stmt_alloc(int_loc); + +@@ -2608,20 +2523,17 @@ static struct stmt *json_parse_log_stmt(struct json_ctx *ctx, + stmt->log.level = level; + stmt->log.flags |= STMT_LOG_LEVEL; + } +- if (!json_unpack(value, "{s:o}", "flags", &jflags)) { +- int flags = json_parse_log_flags(ctx, jflags); +- +- if (flags < 0) { +- stmt_free(stmt); +- return NULL; +- } +- stmt->log.logflags = flags; ++ flags = parse_flags_array(ctx, value, "flags", json_parse_log_flag); ++ if (flags < 0) { ++ stmt_free(stmt); ++ return NULL; + } ++ stmt->log.logflags = flags; ++ + return stmt; + } + +-static int json_parse_synproxy_flag(struct json_ctx *ctx, +- json_t *root, int *flags) ++static unsigned int json_parse_synproxy_flag(const char *flag) + { + const struct { + const char *flag; +@@ -2630,54 +2542,19 @@ static int json_parse_synproxy_flag(struct json_ctx *ctx, + { "timestamp", NF_SYNPROXY_OPT_TIMESTAMP }, + { "sack-perm", NF_SYNPROXY_OPT_SACK_PERM }, + }; +- const char *flag; + unsigned int i; + +- assert(flags); +- +- if (!json_is_string(root)) { +- json_error(ctx, "Invalid synproxy flag type %s, expected string.", +- json_typename(root)); +- return 1; +- } +- flag = json_string_value(root); + for (i = 0; i < array_size(flag_tbl); i++) { +- if (!strcmp(flag, flag_tbl[i].flag)) { +- *flags |= flag_tbl[i].val; +- return 0; +- } +- } +- json_error(ctx, "Unknown synproxy flag '%s'.", flag); +- return 1; +-} +- +-static int json_parse_synproxy_flags(struct json_ctx *ctx, json_t *root) +-{ +- int flags = 0; +- json_t *value; +- size_t index; +- +- if (json_is_string(root)) { +- json_parse_synproxy_flag(ctx, root, &flags); +- return flags; +- } else if (!json_is_array(root)) { +- json_error(ctx, "Invalid synproxy flags type %s.", +- json_typename(root)); +- return -1; +- } +- json_array_foreach(root, index, value) { +- if (json_parse_synproxy_flag(ctx, value, &flags)) +- json_error(ctx, "Parsing synproxy flag at index %zu failed.", +- index); ++ if (!strcmp(flag, flag_tbl[i].flag)) ++ return flag_tbl[i].val; + } +- return flags; ++ return 0; + } + + static struct stmt *json_parse_synproxy_stmt(struct json_ctx *ctx, + const char *key, json_t *value) + { + struct stmt *stmt = NULL; +- json_t *jflags; + int tmp, flags; + + if (json_typeof(value) == JSON_NULL) { +@@ -2707,15 +2584,16 @@ static struct stmt *json_parse_synproxy_stmt(struct json_ctx *ctx, + stmt->synproxy.wscale = tmp; + stmt->synproxy.flags |= NF_SYNPROXY_OPT_WSCALE; + } +- if (!json_unpack(value, "{s:o}", "flags", &jflags)) { ++ ++ flags = parse_flags_array(ctx, value, "flags", ++ json_parse_synproxy_flag); ++ if (flags < 0) { ++ stmt_free(stmt); ++ return NULL; ++ } ++ if (flags) { + if (!stmt) + stmt = synproxy_stmt_alloc(int_loc); +- flags = json_parse_synproxy_flags(ctx, jflags); +- +- if (flags < 0) { +- stmt_free(stmt); +- return NULL; +- } + stmt->synproxy.flags |= flags; + } + +@@ -2810,14 +2688,12 @@ static struct stmt *json_parse_meter_stmt(struct json_ctx *ctx, + return stmt; + } + +-static int queue_flag_parse(const char *name, uint16_t *flags) ++static unsigned int queue_flag_parse(const char *name) + { + if (!strcmp(name, "bypass")) +- *flags |= NFT_QUEUE_FLAG_BYPASS; ++ return NFT_QUEUE_FLAG_BYPASS; + else if (!strcmp(name, "fanout")) +- *flags |= NFT_QUEUE_FLAG_CPU_FANOUT; +- else +- return 1; ++ return NFT_QUEUE_FLAG_CPU_FANOUT; + return 0; + } + +@@ -2825,8 +2701,8 @@ static struct stmt *json_parse_queue_stmt(struct json_ctx *ctx, + const char *key, json_t *value) + { + struct expr *qexpr = NULL; +- uint16_t flags = 0; + json_t *tmp; ++ int flags; + + if (!json_unpack(value, "{s:o}", "num", &tmp)) { + qexpr = json_parse_stmt_expr(ctx, tmp); +@@ -2835,43 +2711,13 @@ static struct stmt *json_parse_queue_stmt(struct json_ctx *ctx, + return NULL; + } + } +- if (!json_unpack(value, "{s:o}", "flags", &tmp)) { +- const char *flag; +- size_t index; +- json_t *val; +- +- if (json_is_string(tmp)) { +- flag = json_string_value(tmp); +- +- if (queue_flag_parse(flag, &flags)) { +- json_error(ctx, "Invalid queue flag '%s'.", +- flag); +- expr_free(qexpr); +- return NULL; +- } +- } else if (!json_is_array(tmp)) { +- json_error(ctx, "Unexpected object type in queue flags."); +- expr_free(qexpr); +- return NULL; +- } +- +- json_array_foreach(tmp, index, val) { +- if (!json_is_string(val)) { +- json_error(ctx, "Invalid object in queue flag array at index %zu.", +- index); +- expr_free(qexpr); +- return NULL; +- } +- flag = json_string_value(val); + +- if (queue_flag_parse(flag, &flags)) { +- json_error(ctx, "Invalid queue flag '%s'.", +- flag); +- expr_free(qexpr); +- return NULL; +- } +- } ++ flags = parse_flags_array(ctx, value, "flags", queue_flag_parse); ++ if (flags < 0) { ++ expr_free(qexpr); ++ return NULL; + } ++ + return queue_stmt_alloc(int_loc, qexpr, flags); + } + +@@ -2982,45 +2828,6 @@ static struct stmt *json_parse_stmt(struct json_ctx *ctx, json_t *root) + return NULL; + } + +-static int json_parse_table_flags(struct json_ctx *ctx, json_t *root, +- enum table_flags *flags) +-{ +- json_t *tmp, *tmp2; +- size_t index; +- int flag; +- +- if (json_unpack(root, "{s:o}", "flags", &tmp)) +- return 0; +- +- if (json_is_string(tmp)) { +- flag = parse_table_flag(json_string_value(tmp)); +- if (flag) { +- *flags = flag; +- return 0; +- } +- json_error(ctx, "Invalid table flag '%s'.", +- json_string_value(tmp)); +- return 1; +- } +- if (!json_is_array(tmp)) { +- json_error(ctx, "Unexpected table flags value."); +- return 1; +- } +- json_array_foreach(tmp, index, tmp2) { +- if (json_is_string(tmp2)) { +- flag = parse_table_flag(json_string_value(tmp2)); +- +- if (flag) { +- *flags |= flag; +- continue; +- } +- } +- json_error(ctx, "Invalid table flag at index %zu.", index); +- return 1; +- } +- return 0; +-} +- + static struct cmd *json_parse_cmd_add_table(struct json_ctx *ctx, json_t *root, + enum cmd_ops op, enum cmd_obj obj) + { +@@ -3029,7 +2836,7 @@ static struct cmd *json_parse_cmd_add_table(struct json_ctx *ctx, json_t *root, + .table.location = *int_loc, + }; + struct table *table = NULL; +- enum table_flags flags = 0; ++ int flags = 0; + + if (json_unpack_err(ctx, root, "{s:s}", + "family", &family)) +@@ -3040,9 +2847,10 @@ static struct cmd *json_parse_cmd_add_table(struct json_ctx *ctx, json_t *root, + return NULL; + + json_unpack(root, "{s:s}", "comment", &comment); +- if (json_parse_table_flags(ctx, root, &flags)) +- return NULL; + ++ flags = parse_flags_array(ctx, root, "flags", parse_table_flag); ++ if (flags < 0) ++ return NULL; + } else if (op == CMD_DELETE && + json_unpack(root, "{s:s}", "name", &h.table.name) && + json_unpack(root, "{s:I}", "handle", &h.handle.id)) { +@@ -3314,7 +3122,7 @@ static int string_to_nft_object(const char *str) + return 0; + } + +-static int string_to_set_flag(const char *str) ++static unsigned int string_to_set_flag(const char *str) + { + const struct { + enum nft_set_flags val; +@@ -3341,6 +3149,7 @@ static struct cmd *json_parse_cmd_add_set(struct json_ctx *ctx, json_t *root, + const char *family = "", *policy; + json_t *tmp, *stmt_json; + struct set *set; ++ int flags; + + if (json_unpack_err(ctx, root, "{s:s, s:s}", + "family", &family, +@@ -3423,23 +3232,16 @@ static struct cmd *json_parse_cmd_add_set(struct json_ctx *ctx, json_t *root, + return NULL; + } + } +- if (!json_unpack(root, "{s:o}", "flags", &tmp)) { +- json_t *value; +- size_t index; +- +- json_array_foreach(tmp, index, value) { +- int flag; + +- if (!json_is_string(value) || +- !(flag = string_to_set_flag(json_string_value(value)))) { +- json_error(ctx, "Invalid set flag at index %zu.", index); +- set_free(set); +- handle_free(&h); +- return NULL; +- } +- set->flags |= flag; +- } ++ flags = parse_flags_array(ctx, root, "flags", string_to_set_flag); ++ if (flags < 0) { ++ json_error(ctx, "Invalid set flags in set '%s'.", h.set.name); ++ set_free(set); ++ handle_free(&h); ++ return NULL; + } ++ set->flags |= flags; ++ + if (!json_unpack(root, "{s:o}", "elem", &tmp)) { + set->init = json_parse_set_expr(ctx, "elem", tmp); + if (!set->init) { +@@ -3624,7 +3426,6 @@ static struct cmd *json_parse_cmd_add_object(struct json_ctx *ctx, + int inv = 0, flags = 0, i, j; + struct handle h = { 0 }; + struct obj *obj; +- json_t *jflags; + + if (json_unpack_err(ctx, root, "{s:s, s:s}", + "family", &family, +@@ -3804,13 +3605,12 @@ static struct cmd *json_parse_cmd_add_object(struct json_ctx *ctx, + obj->synproxy.wscale = j; + obj->synproxy.flags |= NF_SYNPROXY_OPT_MSS; + obj->synproxy.flags |= NF_SYNPROXY_OPT_WSCALE; +- if (!json_unpack(root, "{s:o}", "flags", &jflags)) { +- flags = json_parse_synproxy_flags(ctx, jflags); +- if (flags < 0) +- goto err_free_obj; ++ flags = parse_flags_array(ctx, root, "flags", ++ json_parse_synproxy_flag); ++ if (flags < 0) ++ goto err_free_obj; + +- obj->synproxy.flags |= flags; +- } ++ obj->synproxy.flags |= flags; + break; + default: + BUG("Invalid CMD '%d'", cmd_obj); diff --git a/0196-json-Print-single-synproxy-flags-as-non-array.patch b/0196-json-Print-single-synproxy-flags-as-non-array.patch new file mode 100644 index 0000000..59faa3a --- /dev/null +++ b/0196-json-Print-single-synproxy-flags-as-non-array.patch @@ -0,0 +1,42 @@ +From 85b608dfcb7718e1909862a3617501ca3dd733c2 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:13:20 +0200 +Subject: [PATCH] json: Print single synproxy flags as non-array + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 38f99ee84fe6bd029fbc3d1389f42ab82fa9789c + +commit 38f99ee84fe6bd029fbc3d1389f42ab82fa9789c +Author: Phil Sutter +Date: Thu May 8 16:44:39 2025 +0200 + + json: Print single synproxy flags as non-array + + Signed-off-by: Phil Sutter + +Signed-off-by: Phil Sutter +--- + src/json.c | 8 ++++++-- + 1 file changed, 6 insertions(+), 2 deletions(-) + +diff --git a/src/json.c b/src/json.c +index 1998766..2a94066 100644 +--- a/src/json.c ++++ b/src/json.c +@@ -1667,10 +1667,14 @@ json_t *synproxy_stmt_json(const struct stmt *stmt, struct output_ctx *octx) + if (stmt->synproxy.flags & NF_SYNPROXY_OPT_SACK_PERM) + json_array_append_new(flags, json_string("sack-perm")); + +- if (json_array_size(flags) > 0) ++ if (json_array_size(flags) > 1) { + json_object_set_new(root, "flags", flags); +- else ++ } else { ++ if (json_array_size(flags)) ++ json_object_set(root, "flags", ++ json_array_get(flags, 0)); + json_decref(flags); ++ } + + if (!json_object_size(root)) { + json_decref(root); diff --git a/0197-json-Print-single-set-flag-as-non-array.patch b/0197-json-Print-single-set-flag-as-non-array.patch new file mode 100644 index 0000000..6e0b07a --- /dev/null +++ b/0197-json-Print-single-set-flag-as-non-array.patch @@ -0,0 +1,39 @@ +From d433a3d688bf1ad724e06c57f3a8f14ddb655855 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:13:20 +0200 +Subject: [PATCH] json: Print single set flag as non-array + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 6bedb12af1658562f277ca68d74cf1e9e7433a08 +Conflicts: Dropped changes to non-existent .json-nft dumps + +commit 6bedb12af1658562f277ca68d74cf1e9e7433a08 +Author: Phil Sutter +Date: Thu May 8 16:39:24 2025 +0200 + + json: Print single set flag as non-array + + The code obviously intended to do this already but got the array length + check wrong. + + Fixes: e70354f53e9f6 ("libnftables: Implement JSON output support") + Signed-off-by: Phil Sutter + +Signed-off-by: Phil Sutter +--- + src/json.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/json.c b/src/json.c +index 2a94066..c1927ab 100644 +--- a/src/json.c ++++ b/src/json.c +@@ -189,7 +189,7 @@ static json_t *set_print_json(struct output_ctx *octx, const struct set *set) + if (set->flags & NFT_SET_EVAL) + json_array_append_new(tmp, json_pack("s", "dynamic")); + +- if (json_array_size(tmp) > 0) { ++ if (json_array_size(tmp) > 1) { + json_object_set_new(root, "flags", tmp); + } else { + if (json_array_size(tmp)) diff --git a/0198-tests-shell-Add-test-case-for-JSON-flags-arrays.patch b/0198-tests-shell-Add-test-case-for-JSON-flags-arrays.patch new file mode 100644 index 0000000..9a27f9c --- /dev/null +++ b/0198-tests-shell-Add-test-case-for-JSON-flags-arrays.patch @@ -0,0 +1,220 @@ +From d89f52ad52e344b6e37fb1273e0db5b52a01d5df Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:13:44 +0200 +Subject: [PATCH] tests: shell: Add test case for JSON 'flags' arrays + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 77c2ecee9bc7025aa6582696f94ee33ee644ecc7 + +commit 77c2ecee9bc7025aa6582696f94ee33ee644ecc7 +Author: Phil Sutter +Date: Thu May 8 17:34:18 2025 +0200 + + tests: shell: Add test case for JSON 'flags' arrays + + Ensure these arrays are reduced if containing just a single item and + parser interprets them correctly in any case. + + Signed-off-by: Phil Sutter + +Signed-off-by: Phil Sutter +--- + tests/shell/testcases/json/single_flag | 189 +++++++++++++++++++++++++ + 1 file changed, 189 insertions(+) + create mode 100755 tests/shell/testcases/json/single_flag + +diff --git a/tests/shell/testcases/json/single_flag b/tests/shell/testcases/json/single_flag +new file mode 100755 +index 0000000..41fab63 +--- /dev/null ++++ b/tests/shell/testcases/json/single_flag +@@ -0,0 +1,189 @@ ++#!/bin/bash ++# ++# Test various "flags" properties in JSON syntax: ++# - single item arrays are abbreviated as non-array in output ++# - both non-array and single item array accepted in input ++# - single and multiple item values are correctly printed in output and ++# recognized in input (checked against standard syntax input/output) ++ ++# NFT_TEST_REQUIRES(NFT_TEST_HAVE_json) ++ ++set -e ++ ++json_sanitize() { ++ sed -e 's/{"metainfo": {[^}]*}}, //' \ ++ -e 's/\("handle":\) [0-9]*/\1 0/g' ++} ++back_n_forth() { # (std, json) ++ $NFT flush ruleset ++ $NFT -f - <<< "$1" ++ diff --label "line ${BASH_LINENO[0]}: JSON output" \ ++ --label "line ${BASH_LINENO[0]}: JSON expect" \ ++ -u <($NFT -j list ruleset | json_sanitize) <(echo "$2") ++ ++ $NFT flush ruleset ++ $NFT -j -f - <<< "$2" ++ diff --label "line ${BASH_LINENO[0]}: std output" \ ++ --label "line ${BASH_LINENO[0]}: std expect" \ ++ -u <($NFT list ruleset) <(echo "$1") ++} ++json_equiv() { # (json_in, json_out) ++ $NFT flush ruleset ++ $NFT -j -f - <<< "$1" ++ diff --label "line ${BASH_LINENO[0]}: JSON equiv output" \ ++ --label "line ${BASH_LINENO[0]}: JSON equiv expect" \ ++ -u <($NFT -j list ruleset | json_sanitize) <(echo "$2") ++} ++ ++# ++# test table flags ++# ++ ++STD_TABLE_1="table ip t { ++ flags dormant ++}" ++JSON_TABLE_1='{"nftables": [{"table": {"family": "ip", "name": "t", "handle": 0, "flags": "dormant"}}]}' ++JSON_TABLE_1_EQUIV=$(sed 's/\("flags":\) \([^}]*\)/\1 [\2]/' <<< "$JSON_TABLE_1") ++ ++STD_TABLE_2=$(sed 's/\(flags dormant\)/\1,persist/' <<< "$STD_TABLE_1") ++JSON_TABLE_2=$(sed 's/\("flags":\) \("dormant"\)/\1 [\2, "persist"]/' <<< "$JSON_TABLE_1") ++ ++back_n_forth "$STD_TABLE_1" "$JSON_TABLE_1" ++json_equiv "$JSON_TABLE_1_EQUIV" "$JSON_TABLE_1" ++back_n_forth "$STD_TABLE_2" "$JSON_TABLE_2" ++ ++# ++# test set flags ++# ++ ++STD_SET_1="table ip t { ++ set s { ++ type inet_proto ++ flags interval ++ } ++}" ++JSON_SET_1='{"nftables": [{"table": {"family": "ip", "name": "t", "handle": 0}}, {"set": {"family": "ip", "name": "s", "table": "t", "type": "inet_proto", "handle": 0, "flags": "interval"}}]}' ++JSON_SET_1_EQUIV=$(sed 's/\("flags":\) \([^}]*\)/\1 [\2]/' <<< "$JSON_SET_1") ++ ++STD_SET_2=$(sed 's/\(flags interval\)/\1,timeout/' <<< "$STD_SET_1") ++JSON_SET_2=$(sed 's/\("flags":\) \("interval"\)/\1 [\2, "timeout"]/' <<< "$JSON_SET_1") ++ ++back_n_forth "$STD_SET_1" "$JSON_SET_1" ++json_equiv "$JSON_SET_1_EQUIV" "$JSON_SET_1" ++back_n_forth "$STD_SET_2" "$JSON_SET_2" ++ ++# ++# test fib expression flags ++# ++ ++STD_FIB_1="table ip t { ++ chain c { ++ fib saddr oif exists ++ } ++}" ++JSON_FIB_1='{"nftables": [{"table": {"family": "ip", "name": "t", "handle": 0}}, {"chain": {"family": "ip", "table": "t", "name": "c", "handle": 0}}, {"rule": {"family": "ip", "table": "t", "chain": "c", "handle": 0, "expr": [{"match": {"op": "==", "left": {"fib": {"result": "oif", "flags": "saddr"}}, "right": true}}]}}]}' ++JSON_FIB_1_EQUIV=$(sed 's/\("flags":\) \([^}]*\)/\1 [\2]/' <<< "$JSON_FIB_1") ++ ++STD_FIB_2=$(sed 's/\(fib saddr\)/\1 . iif/' <<< "$STD_FIB_1") ++JSON_FIB_2=$(sed 's/\("flags":\) \("saddr"\)/\1 [\2, "iif"]/' <<< "$JSON_FIB_1") ++ ++back_n_forth "$STD_FIB_1" "$JSON_FIB_1" ++json_equiv "$JSON_FIB_1_EQUIV" "$JSON_FIB_1" ++back_n_forth "$STD_FIB_2" "$JSON_FIB_2" ++ ++# ++# test nat statement flags ++# ++ ++STD_NAT_1="table ip t { ++ chain c { ++ dnat to 192.168.0.0/24 persistent ++ } ++}" ++JSON_NAT_1='{"nftables": [{"table": {"family": "ip", "name": "t", "handle": 0}}, {"chain": {"family": "ip", "table": "t", "name": "c", "handle": 0}}, {"rule": {"family": "ip", "table": "t", "chain": "c", "handle": 0, "expr": [{"dnat": {"addr": {"prefix": {"addr": "192.168.0.0", "len": 24}}, "flags": "persistent"}}]}}]}' ++JSON_NAT_1_EQUIV=$(sed 's/\("flags":\) \([^}]*\)/\1 [\2]/' <<< "$JSON_NAT_1") ++ ++STD_NAT_2=$(sed 's/\(persistent\)/random,\1/' <<< "$STD_NAT_1") ++JSON_NAT_2=$(sed 's/\("flags":\) \("persistent"\)/\1 ["random", \2]/' <<< "$JSON_NAT_1") ++ ++back_n_forth "$STD_NAT_1" "$JSON_NAT_1" ++json_equiv "$JSON_NAT_1_EQUIV" "$JSON_NAT_1" ++back_n_forth "$STD_NAT_2" "$JSON_NAT_2" ++ ++# ++# test log statement flags ++# ++ ++STD_LOG_1="table ip t { ++ chain c { ++ log flags tcp sequence ++ } ++}" ++JSON_LOG_1='{"nftables": [{"table": {"family": "ip", "name": "t", "handle": 0}}, {"chain": {"family": "ip", "table": "t", "name": "c", "handle": 0}}, {"rule": {"family": "ip", "table": "t", "chain": "c", "handle": 0, "expr": [{"log": {"flags": "tcp sequence"}}]}}]}' ++JSON_LOG_1_EQUIV=$(sed 's/\("flags":\) \([^}]*\)/\1 [\2]/' <<< "$JSON_LOG_1") ++ ++STD_LOG_2=$(sed 's/\(tcp sequence\)/\1,options/' <<< "$STD_LOG_1") ++JSON_LOG_2=$(sed 's/\("flags":\) \("tcp sequence"\)/\1 [\2, "tcp options"]/' <<< "$JSON_LOG_1") ++ ++back_n_forth "$STD_LOG_1" "$JSON_LOG_1" ++json_equiv "$JSON_LOG_1_EQUIV" "$JSON_LOG_1" ++back_n_forth "$STD_LOG_2" "$JSON_LOG_2" ++ ++# ++# test synproxy statement flags ++# ++ ++STD_SYNPROXY_1="table ip t { ++ chain c { ++ synproxy sack-perm ++ } ++}" ++JSON_SYNPROXY_1='{"nftables": [{"table": {"family": "ip", "name": "t", "handle": 0}}, {"chain": {"family": "ip", "table": "t", "name": "c", "handle": 0}}, {"rule": {"family": "ip", "table": "t", "chain": "c", "handle": 0, "expr": [{"synproxy": {"flags": "sack-perm"}}]}}]}' ++JSON_SYNPROXY_1_EQUIV=$(sed 's/\("flags":\) \([^}]*\)/\1 [\2]/' <<< "$JSON_SYNPROXY_1") ++ ++STD_SYNPROXY_2=$(sed 's/\(sack-perm\)/timestamp \1/' <<< "$STD_SYNPROXY_1") ++JSON_SYNPROXY_2=$(sed 's/\("flags":\) \("sack-perm"\)/\1 ["timestamp", \2]/' <<< "$JSON_SYNPROXY_1") ++ ++back_n_forth "$STD_SYNPROXY_1" "$JSON_SYNPROXY_1" ++json_equiv "$JSON_SYNPROXY_1_EQUIV" "$JSON_SYNPROXY_1" ++back_n_forth "$STD_SYNPROXY_2" "$JSON_SYNPROXY_2" ++ ++# ++# test synproxy object flags ++# ++ ++STD_SYNPROXY_OBJ_1="table ip t { ++ synproxy s { ++ mss 1280 ++ wscale 64 ++ sack-perm ++ } ++}" ++JSON_SYNPROXY_OBJ_1='{"nftables": [{"table": {"family": "ip", "name": "t", "handle": 0}}, {"synproxy": {"family": "ip", "name": "s", "table": "t", "handle": 0, "mss": 1280, "wscale": 64, "flags": "sack-perm"}}]}' ++JSON_SYNPROXY_OBJ_1_EQUIV=$(sed 's/\("flags":\) \([^}]*\)/\1 [\2]/' <<< "$JSON_SYNPROXY_OBJ_1") ++ ++STD_SYNPROXY_OBJ_2=$(sed 's/ \(sack-perm\)/timestamp \1/' <<< "$STD_SYNPROXY_OBJ_1") ++JSON_SYNPROXY_OBJ_2=$(sed 's/\("flags":\) \("sack-perm"\)/\1 ["timestamp", \2]/' <<< "$JSON_SYNPROXY_OBJ_1") ++ ++back_n_forth "$STD_SYNPROXY_OBJ_1" "$JSON_SYNPROXY_OBJ_1" ++json_equiv "$JSON_SYNPROXY_OBJ_1_EQUIV" "$JSON_SYNPROXY_OBJ_1" ++back_n_forth "$STD_SYNPROXY_OBJ_2" "$JSON_SYNPROXY_OBJ_2" ++ ++# ++# test queue statement flags ++# ++ ++STD_QUEUE_1="table ip t { ++ chain c { ++ queue flags bypass to 1-10 ++ } ++}" ++JSON_QUEUE_1='{"nftables": [{"table": {"family": "ip", "name": "t", "handle": 0}}, {"chain": {"family": "ip", "table": "t", "name": "c", "handle": 0}}, {"rule": {"family": "ip", "table": "t", "chain": "c", "handle": 0, "expr": [{"queue": {"num": {"range": [1, 10]}, "flags": "bypass"}}]}}]}' ++JSON_QUEUE_1_EQUIV=$(sed 's/\("flags":\) \([^}]*\)/\1 [\2]/' <<< "$JSON_QUEUE_1") ++ ++STD_QUEUE_2=$(sed 's/\(bypass\)/\1,fanout/' <<< "$STD_QUEUE_1") ++JSON_QUEUE_2=$(sed 's/\("flags":\) \("bypass"\)/\1 [\2, "fanout"]/' <<< "$JSON_QUEUE_1") ++ ++back_n_forth "$STD_QUEUE_1" "$JSON_QUEUE_1" ++json_equiv "$JSON_QUEUE_1_EQUIV" "$JSON_QUEUE_1" ++back_n_forth "$STD_QUEUE_2" "$JSON_QUEUE_2" diff --git a/0199-json-Print-single-fib-flag-as-non-array.patch b/0199-json-Print-single-fib-flag-as-non-array.patch new file mode 100644 index 0000000..f1bb24f --- /dev/null +++ b/0199-json-Print-single-fib-flag-as-non-array.patch @@ -0,0 +1,48 @@ +From 24a6352951680a8ab6bc2c43f283eb098a8abffb Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:13:44 +0200 +Subject: [PATCH] json: Print single fib flag as non-array + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit dbe5c44f2b891c1d305cc94a7ea9dd963a7b6100 + +commit dbe5c44f2b891c1d305cc94a7ea9dd963a7b6100 +Author: Phil Sutter +Date: Thu May 8 16:40:41 2025 +0200 + + json: Print single fib flag as non-array + + Check array size and reduce the array if possible. + + The zero array length check is dead code here due to the surrounding 'if + (flags)' block, but it's a common idiom one could replace by a shared + routine later. + + Signed-off-by: Phil Sutter + +Signed-off-by: Phil Sutter +--- + src/json.c | 10 +++++++++- + 1 file changed, 9 insertions(+), 1 deletion(-) + +diff --git a/src/json.c b/src/json.c +index c1927ab..daa1fb5 100644 +--- a/src/json.c ++++ b/src/json.c +@@ -928,7 +928,15 @@ json_t *fib_expr_json(const struct expr *expr, struct output_ctx *octx) + } + if (flags) + json_array_append_new(tmp, json_integer(flags)); +- json_object_set_new(root, "flags", tmp); ++ ++ if (json_array_size(tmp) > 1) { ++ json_object_set_new(root, "flags", tmp); ++ } else { ++ if (json_array_size(tmp)) ++ json_object_set(root, "flags", ++ json_array_get(tmp, 0)); ++ json_decref(tmp); ++ } + } + return json_pack("{s:o}", "fib", root); + } diff --git a/0200-json-Accept-more-than-two-operands-in-binary-express.patch b/0200-json-Accept-more-than-two-operands-in-binary-express.patch new file mode 100644 index 0000000..472158d --- /dev/null +++ b/0200-json-Accept-more-than-two-operands-in-binary-express.patch @@ -0,0 +1,380 @@ +From 09efbdc26bc08b23cfaace6ec3a370fbcb47f1d1 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:13:45 +0200 +Subject: [PATCH] json: Accept more than two operands in binary expressions + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 0ac39384fd9e48ff6bcc5605df2cbeb33af64b9e +Conflicts: Dropped changes to non-existent .json-nft dumps + +commit 0ac39384fd9e48ff6bcc5605df2cbeb33af64b9e +Author: Phil Sutter +Date: Wed Mar 20 15:54:54 2024 +0100 + + json: Accept more than two operands in binary expressions + + The most common use case is ORing flags like + + | syn | ack | rst + + but nft seems to be fine with less intuitive stuff like + + | meta mark set ip dscp << 2 << 3 + + so support all of them. + + Signed-off-by: Phil Sutter + +Signed-off-by: Phil Sutter +--- + doc/libnftables-json.adoc | 18 +++--- + src/json.c | 19 +++++- + src/parser_json.c | 12 ++++ + tests/py/inet/tcp.t.json | 67 ++------------------ + tests/py/inet/tcp.t.json.output | 104 +++++++------------------------- + 5 files changed, 66 insertions(+), 154 deletions(-) + +diff --git a/doc/libnftables-json.adoc b/doc/libnftables-json.adoc +index ab4223d..9129cc8 100644 +--- a/doc/libnftables-json.adoc ++++ b/doc/libnftables-json.adoc +@@ -1343,15 +1343,17 @@ Perform kernel Forwarding Information Base lookups. + + === BINARY OPERATION + [verse] +-*{ "|": [* 'EXPRESSION'*,* 'EXPRESSION' *] }* +-*{ "^": [* 'EXPRESSION'*,* 'EXPRESSION' *] }* +-*{ "&": [* 'EXPRESSION'*,* 'EXPRESSION' *] }* +-*{ "+<<+": [* 'EXPRESSION'*,* 'EXPRESSION' *] }* +-*{ ">>": [* 'EXPRESSION'*,* 'EXPRESSION' *] }* +- +-All binary operations expect an array of exactly two expressions, of which the ++*{ "|": [* 'EXPRESSION'*,* 'EXPRESSIONS' *] }* ++*{ "^": [* 'EXPRESSION'*,* 'EXPRESSIONS' *] }* ++*{ "&": [* 'EXPRESSION'*,* 'EXPRESSIONS' *] }* ++*{ "+<<+": [* 'EXPRESSION'*,* 'EXPRESSIONS' *] }* ++*{ ">>": [* 'EXPRESSION'*,* 'EXPRESSIONS' *] }* ++'EXPRESSIONS' := 'EXPRESSION' | 'EXPRESSION'*,* 'EXPRESSIONS' ++ ++All binary operations expect an array of at least two expressions, of which the + first element denotes the left hand side and the second one the right hand +-side. ++side. Extra elements are accepted in the given array and appended to the term ++accordingly. + + === VERDICT + [verse] +diff --git a/src/json.c b/src/json.c +index daa1fb5..089c251 100644 +--- a/src/json.c ++++ b/src/json.c +@@ -551,11 +551,24 @@ json_t *flagcmp_expr_json(const struct expr *expr, struct output_ctx *octx) + "right", expr_print_json(expr->flagcmp.value, octx)); + } + ++static json_t * ++__binop_expr_json(int op, const struct expr *expr, struct output_ctx *octx) ++{ ++ json_t *a = json_array(); ++ ++ if (expr->etype == EXPR_BINOP && expr->op == op) { ++ json_array_extend(a, __binop_expr_json(op, expr->left, octx)); ++ json_array_extend(a, __binop_expr_json(op, expr->right, octx)); ++ } else { ++ json_array_append_new(a, expr_print_json(expr, octx)); ++ } ++ return a; ++} ++ + json_t *binop_expr_json(const struct expr *expr, struct output_ctx *octx) + { +- return json_pack("{s:[o, o]}", expr_op_symbols[expr->op], +- expr_print_json(expr->left, octx), +- expr_print_json(expr->right, octx)); ++ return json_pack("{s:o}", expr_op_symbols[expr->op], ++ __binop_expr_json(expr->op, expr, octx)); + } + + json_t *relational_expr_json(const struct expr *expr, struct output_ctx *octx) +diff --git a/src/parser_json.c b/src/parser_json.c +index dc038f5..05dab1a 100644 +--- a/src/parser_json.c ++++ b/src/parser_json.c +@@ -1221,6 +1221,18 @@ static struct expr *json_parse_binop_expr(struct json_ctx *ctx, + return NULL; + } + ++ if (json_array_size(root) > 2) { ++ left = json_parse_primary_expr(ctx, json_array_get(root, 0)); ++ right = json_parse_primary_expr(ctx, json_array_get(root, 1)); ++ left = binop_expr_alloc(int_loc, thisop, left, right); ++ for (i = 2; i < json_array_size(root); i++) { ++ jright = json_array_get(root, i); ++ right = json_parse_primary_expr(ctx, jright); ++ left = binop_expr_alloc(int_loc, thisop, left, right); ++ } ++ return left; ++ } ++ + if (json_unpack_err(ctx, root, "[o, o!]", &jleft, &jright)) + return NULL; + +diff --git a/tests/py/inet/tcp.t.json b/tests/py/inet/tcp.t.json +index d3a846c..bd589cf 100644 +--- a/tests/py/inet/tcp.t.json ++++ b/tests/py/inet/tcp.t.json +@@ -954,12 +954,12 @@ + } + }, + { +- "|": [ "fin", { "|": [ "syn", { "|": [ "rst", { "|": [ "psh", { "|": [ "ack", { "|": [ "urg", { "|": [ "ecn", "cwr" ] } ] } ] } ] } ] } ] } ] ++ "|": [ "fin", "syn", "rst", "psh", "ack", "urg", "ecn", "cwr" ] + } + ] + }, + "op": "==", +- "right": { "|": [ "fin", { "|": [ "syn", { "|": [ "rst", { "|": [ "psh", { "|": [ "ack", { "|": [ "urg", { "|": [ "ecn", "cwr" ] } ] } ] } ] } ] } ] } ] } ++ "right": { "|": [ "fin", "syn", "rst", "psh", "ack", "urg", "ecn", "cwr" ] } + } + } + ] +@@ -1395,55 +1395,15 @@ + "protocol": "tcp" + } + }, +- { +- "|": [ +- { +- "|": [ +- { +- "|": [ +- { +- "|": [ +- { +- "|": [ +- "fin", +- "syn" +- ] +- }, +- "rst" +- ] +- }, +- "psh" +- ] +- }, +- "ack" +- ] +- }, +- "urg" +- ] +- } ++ { "|": [ "fin", "syn", "rst", "psh", "ack", "urg" ] } + ] + }, + "op": "==", + "right": { + "set": [ +- { +- "|": [ +- { +- "|": [ +- "fin", +- "psh" +- ] +- }, +- "ack" +- ] +- }, ++ { "|": [ "fin", "psh", "ack" ] }, + "fin", +- { +- "|": [ +- "psh", +- "ack" +- ] +- }, ++ { "|": [ "psh", "ack" ] }, + "ack" + ] + } +@@ -1780,22 +1740,7 @@ + "protocol": "tcp" + } + }, +- { +- "|": [ +- { +- "|": [ +- { +- "|": [ +- "fin", +- "syn" +- ] +- }, +- "rst" +- ] +- }, +- "ack" +- ] +- } ++ { "|": [ "fin", "syn", "rst", "ack" ] } + ] + }, + "op": "!=", +diff --git a/tests/py/inet/tcp.t.json.output b/tests/py/inet/tcp.t.json.output +index e186e12..3f03c0d 100644 +--- a/tests/py/inet/tcp.t.json.output ++++ b/tests/py/inet/tcp.t.json.output +@@ -155,27 +155,11 @@ + }, + { + "|": [ +- { +- "|": [ +- { +- "|": [ +- { +- "|": [ +- { +- "|": [ +- "fin", +- "syn" +- ] +- }, +- "rst" +- ] +- }, +- "psh" +- ] +- }, +- "ack" +- ] +- }, ++ "fin", ++ "syn", ++ "rst", ++ "psh", ++ "ack", + "urg" + ] + } +@@ -187,12 +171,8 @@ + "fin", + { + "|": [ +- { +- "|": [ +- "fin", +- "psh" +- ] +- }, ++ "fin", ++ "psh", + "ack" + ] + }, +@@ -280,17 +260,9 @@ + }, + { + "|": [ +- { +- "|": [ +- { +- "|": [ +- "fin", +- "syn" +- ] +- }, +- "rst" +- ] +- }, ++ "fin", ++ "syn", ++ "rst", + "ack" + ] + } +@@ -316,17 +288,9 @@ + }, + { + "|": [ +- { +- "|": [ +- { +- "|": [ +- "fin", +- "syn" +- ] +- }, +- "rst" +- ] +- }, ++ "fin", ++ "syn", ++ "rst", + "ack" + ] + } +@@ -352,17 +316,9 @@ + }, + { + "|": [ +- { +- "|": [ +- { +- "|": [ +- "fin", +- "syn" +- ] +- }, +- "rst" +- ] +- }, ++ "fin", ++ "syn", ++ "rst", + "ack" + ] + } +@@ -388,17 +344,9 @@ + }, + { + "|": [ +- { +- "|": [ +- { +- "|": [ +- "fin", +- "syn" +- ] +- }, +- "rst" +- ] +- }, ++ "fin", ++ "syn", ++ "rst", + "ack" + ] + } +@@ -429,17 +377,9 @@ + }, + { + "|": [ +- { +- "|": [ +- { +- "|": [ +- "fin", +- "syn" +- ] +- }, +- "rst" +- ] +- }, ++ "fin", ++ "syn", ++ "rst", + "ack" + ] + } diff --git a/0201-json-Fix-for-memleak-in-__binop_expr_json.patch b/0201-json-Fix-for-memleak-in-__binop_expr_json.patch new file mode 100644 index 0000000..62119e3 --- /dev/null +++ b/0201-json-Fix-for-memleak-in-__binop_expr_json.patch @@ -0,0 +1,93 @@ +From 3bcd138f2baa8578caf261768e67b58ff3372f91 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:14:03 +0200 +Subject: [PATCH] json: Fix for memleak in __binop_expr_json + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit a0a15e4dd0576bc4efd9b01fdd4ee1c565effac9 + +commit a0a15e4dd0576bc4efd9b01fdd4ee1c565effac9 +Author: Phil Sutter +Date: Wed Apr 24 23:35:00 2024 +0200 + + json: Fix for memleak in __binop_expr_json + + When merging the JSON arrays generated for LHS and RHS of nested binop + expressions, the emptied array objects leak if their reference is not + decremented. + + Fix this and tidy up other spots which did it right already by + introducing a json_array_extend wrapper. + + Reported-by: Pablo Neira Ayuso + Fixes: 0ac39384fd9e4 ("json: Accept more than two operands in binary expressions") + Signed-off-by: Phil Sutter + +Signed-off-by: Phil Sutter +--- + src/json.c | 24 ++++++++++++++++-------- + 1 file changed, 16 insertions(+), 8 deletions(-) + +diff --git a/src/json.c b/src/json.c +index 089c251..516045b 100644 +--- a/src/json.c ++++ b/src/json.c +@@ -42,6 +42,15 @@ + }) + #endif + ++static int json_array_extend_new(json_t *array, json_t *other_array) ++{ ++ int ret; ++ ++ ret = json_array_extend(array, other_array); ++ json_decref(other_array); ++ return ret; ++} ++ + static json_t *expr_print_json(const struct expr *expr, struct output_ctx *octx) + { + const struct expr_ops *ops; +@@ -557,8 +566,10 @@ __binop_expr_json(int op, const struct expr *expr, struct output_ctx *octx) + json_t *a = json_array(); + + if (expr->etype == EXPR_BINOP && expr->op == op) { +- json_array_extend(a, __binop_expr_json(op, expr->left, octx)); +- json_array_extend(a, __binop_expr_json(op, expr->right, octx)); ++ json_array_extend_new(a, ++ __binop_expr_json(op, expr->left, octx)); ++ json_array_extend_new(a, ++ __binop_expr_json(op, expr->right, octx)); + } else { + json_array_append_new(a, expr_print_json(expr, octx)); + } +@@ -1761,8 +1772,7 @@ static json_t *table_print_json_full(struct netlink_ctx *ctx, + } + } + +- json_array_extend(root, rules); +- json_decref(rules); ++ json_array_extend_new(root, rules); + + return root; + } +@@ -1770,7 +1780,7 @@ static json_t *table_print_json_full(struct netlink_ctx *ctx, + static json_t *do_list_ruleset_json(struct netlink_ctx *ctx, struct cmd *cmd) + { + unsigned int family = cmd->handle.family; +- json_t *root = json_array(), *tmp; ++ json_t *root = json_array(); + struct table *table; + + list_for_each_entry(table, &ctx->nft->cache.table_cache.list, cache.list) { +@@ -1778,9 +1788,7 @@ static json_t *do_list_ruleset_json(struct netlink_ctx *ctx, struct cmd *cmd) + table->handle.family != family) + continue; + +- tmp = table_print_json_full(ctx, table); +- json_array_extend(root, tmp); +- json_decref(tmp); ++ json_array_extend_new(root, table_print_json_full(ctx, table)); + } + + return root; diff --git a/0202-json-Introduce-json_add_array_new.patch b/0202-json-Introduce-json_add_array_new.patch new file mode 100644 index 0000000..8bbf891 --- /dev/null +++ b/0202-json-Introduce-json_add_array_new.patch @@ -0,0 +1,219 @@ +From b5ed3db3e50711c3bee617ae6c9688060ce691fd Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:14:03 +0200 +Subject: [PATCH] json: Introduce json_add_array_new() + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit a740f2036ad0d50b4a91e110611563809dac420a + +commit a740f2036ad0d50b4a91e110611563809dac420a +Author: Phil Sutter +Date: Thu May 8 17:28:02 2025 +0200 + + json: Introduce json_add_array_new() + + Propagate nat_stmt_add_array() to a generic helper for use in all spots + adding an array property which may reduce to a single item or even not + exist at all. + + Signed-off-by: Phil Sutter + +Signed-off-by: Phil Sutter +--- + src/json.c | 99 +++++++++++++----------------------------------------- + 1 file changed, 24 insertions(+), 75 deletions(-) + +diff --git a/src/json.c b/src/json.c +index 516045b..01c5dbc 100644 +--- a/src/json.c ++++ b/src/json.c +@@ -51,6 +51,18 @@ static int json_array_extend_new(json_t *array, json_t *other_array) + return ret; + } + ++static void json_add_array_new(json_t *obj, const char *name, json_t *array) ++{ ++ if (json_array_size(array) > 1) { ++ json_object_set_new(obj, name, array); ++ } else { ++ if (json_array_size(array)) ++ json_object_set(obj, name, ++ json_array_get(array, 0)); ++ json_decref(array); ++ } ++} ++ + static json_t *expr_print_json(const struct expr *expr, struct output_ctx *octx) + { + const struct expr_ops *ops; +@@ -197,14 +209,7 @@ static json_t *set_print_json(struct output_ctx *octx, const struct set *set) + json_array_append_new(tmp, json_pack("s", "timeout")); + if (set->flags & NFT_SET_EVAL) + json_array_append_new(tmp, json_pack("s", "dynamic")); +- +- if (json_array_size(tmp) > 1) { +- json_object_set_new(root, "flags", tmp); +- } else { +- if (json_array_size(tmp)) +- json_object_set(root, "flags", json_array_get(tmp, 0)); +- json_decref(tmp); +- } ++ json_add_array_new(root, "flags", tmp); + + if (set->timeout) { + tmp = json_integer(set->timeout / 1000); +@@ -448,19 +453,16 @@ static json_t *obj_print_json(const struct obj *obj) + json_decref(tmp); + break; + case NFT_OBJECT_SYNPROXY: +- flags = json_array(); + tmp = json_pack("{s:i, s:i}", + "mss", obj->synproxy.mss, + "wscale", obj->synproxy.wscale); ++ ++ flags = json_array(); + if (obj->synproxy.flags & NF_SYNPROXY_OPT_TIMESTAMP) + json_array_append_new(flags, json_string("timestamp")); + if (obj->synproxy.flags & NF_SYNPROXY_OPT_SACK_PERM) + json_array_append_new(flags, json_string("sack-perm")); +- +- if (json_array_size(flags) > 0) +- json_object_set_new(tmp, "flags", flags); +- else +- json_decref(flags); ++ json_add_array_new(tmp, "flags", flags); + + json_object_update(root, tmp); + json_decref(tmp); +@@ -514,31 +516,18 @@ static json_t *table_flags_json(const struct table *table) + flags >>= 1; + i++; + } +- switch (json_array_size(root)) { +- case 0: +- json_decref(root); +- return NULL; +- case 1: +- json_unpack(root, "[O]", &tmp); +- json_decref(root); +- root = tmp; +- break; +- } + return root; + } + + static json_t *table_print_json(const struct table *table) + { +- json_t *root, *tmp; ++ json_t *root; + + root = json_pack("{s:s, s:s, s:I}", + "family", family2str(table->handle.family), + "name", table->handle.table.name, + "handle", table->handle.handle.id); +- +- tmp = table_flags_json(table); +- if (tmp) +- json_object_set_new(root, "flags", tmp); ++ json_add_array_new(root, "flags", table_flags_json(table)); + + if (table->comment) + json_object_set_new(root, "comment", json_string(table->comment)); +@@ -953,14 +942,7 @@ json_t *fib_expr_json(const struct expr *expr, struct output_ctx *octx) + if (flags) + json_array_append_new(tmp, json_integer(flags)); + +- if (json_array_size(tmp) > 1) { +- json_object_set_new(root, "flags", tmp); +- } else { +- if (json_array_size(tmp)) +- json_object_set(root, "flags", +- json_array_get(tmp, 0)); +- json_decref(tmp); +- } ++ json_add_array_new(root, "flags", tmp); + } + return json_pack("{s:o}", "fib", root); + } +@@ -1397,14 +1379,7 @@ json_t *log_stmt_json(const struct stmt *stmt, struct output_ctx *octx) + if (stmt->log.logflags & NF_LOG_MACDECODE) + json_array_append_new(flags, json_string("ether")); + } +- if (json_array_size(flags) > 1) { +- json_object_set_new(root, "flags", flags); +- } else { +- if (json_array_size(flags)) +- json_object_set(root, "flags", +- json_array_get(flags, 0)); +- json_decref(flags); +- } ++ json_add_array_new(root, "flags", flags); + + if (!json_object_size(root)) { + json_decref(root); +@@ -1439,18 +1414,6 @@ static json_t *nat_type_flags_json(uint32_t type_flags) + return array; + } + +-static void nat_stmt_add_array(json_t *root, const char *name, json_t *array) +-{ +- if (json_array_size(array) > 1) { +- json_object_set_new(root, name, array); +- } else { +- if (json_array_size(array)) +- json_object_set(root, name, +- json_array_get(array, 0)); +- json_decref(array); +- } +-} +- + json_t *nat_stmt_json(const struct stmt *stmt, struct output_ctx *octx) + { + json_t *root = json_object(); +@@ -1472,12 +1435,12 @@ json_t *nat_stmt_json(const struct stmt *stmt, struct output_ctx *octx) + json_object_set_new(root, "port", + expr_print_json(stmt->nat.proto, octx)); + +- nat_stmt_add_array(root, "flags", array); ++ json_add_array_new(root, "flags", array); + + if (stmt->nat.type_flags) { + array = nat_type_flags_json(stmt->nat.type_flags); + +- nat_stmt_add_array(root, "type_flags", array); ++ json_add_array_new(root, "type_flags", array); + } + + if (!json_object_size(root)) { +@@ -1629,14 +1592,7 @@ json_t *queue_stmt_json(const struct stmt *stmt, struct output_ctx *octx) + json_array_append_new(flags, json_string("bypass")); + if (stmt->queue.flags & NFT_QUEUE_FLAG_CPU_FANOUT) + json_array_append_new(flags, json_string("fanout")); +- if (json_array_size(flags) > 1) { +- json_object_set_new(root, "flags", flags); +- } else { +- if (json_array_size(flags)) +- json_object_set(root, "flags", +- json_array_get(flags, 0)); +- json_decref(flags); +- } ++ json_add_array_new(root, "flags", flags); + + if (!json_object_size(root)) { + json_decref(root); +@@ -1699,14 +1655,7 @@ json_t *synproxy_stmt_json(const struct stmt *stmt, struct output_ctx *octx) + if (stmt->synproxy.flags & NF_SYNPROXY_OPT_SACK_PERM) + json_array_append_new(flags, json_string("sack-perm")); + +- if (json_array_size(flags) > 1) { +- json_object_set_new(root, "flags", flags); +- } else { +- if (json_array_size(flags)) +- json_object_set(root, "flags", +- json_array_get(flags, 0)); +- json_decref(flags); +- } ++ json_add_array_new(root, "flags", flags); + + if (!json_object_size(root)) { + json_decref(root); diff --git a/0203-netlink-Catch-unknown-types-when-deserializing-objec.patch b/0203-netlink-Catch-unknown-types-when-deserializing-objec.patch new file mode 100644 index 0000000..d4b8350 --- /dev/null +++ b/0203-netlink-Catch-unknown-types-when-deserializing-objec.patch @@ -0,0 +1,41 @@ +From 2de3fcf082ef827a5e337958b9145c08b3f334b0 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:14:03 +0200 +Subject: [PATCH] netlink: Catch unknown types when deserializing objects + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 690f19eadde5cb607ec3d8d471c86d558c7229bd + +commit 690f19eadde5cb607ec3d8d471c86d558c7229bd +Author: Phil Sutter +Date: Fri May 16 19:41:19 2025 +0200 + + netlink: Catch unknown types when deserializing objects + + Print an error message and discard the object instead of returning it to + the caller. At least when trying to print it, we would hit an assert() + in obj_type_name() anyway. + + Fixes: 4756d92e517ae ("src: listing of stateful objects") + Signed-off-by: Phil Sutter + +Signed-off-by: Phil Sutter +--- + src/netlink.c | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/src/netlink.c b/src/netlink.c +index 1007571..bf2cf53 100644 +--- a/src/netlink.c ++++ b/src/netlink.c +@@ -1769,6 +1769,10 @@ struct obj *netlink_delinearize_obj(struct netlink_ctx *ctx, + obj->synproxy.flags = + nftnl_obj_get_u32(nlo, NFTNL_OBJ_SYNPROXY_FLAGS); + break; ++ default: ++ netlink_io_error(ctx, NULL, "Unknown object type %u", type); ++ obj_free(obj); ++ return NULL; + } + obj->type = type; + diff --git a/0204-netlink-Avoid-potential-NULL-ptr-deref-parsing-set-e.patch b/0204-netlink-Avoid-potential-NULL-ptr-deref-parsing-set-e.patch new file mode 100644 index 0000000..796841b --- /dev/null +++ b/0204-netlink-Avoid-potential-NULL-ptr-deref-parsing-set-e.patch @@ -0,0 +1,40 @@ +From c43c837b05d3a813a8b826d34aef9c8bb4333dba Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:14:03 +0200 +Subject: [PATCH] netlink: Avoid potential NULL-ptr deref parsing set elem + expressions + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 89645d04106d45d5e23b6d5206777dad1fb3e6bf + +commit 89645d04106d45d5e23b6d5206777dad1fb3e6bf +Author: Phil Sutter +Date: Fri May 16 20:08:05 2025 +0200 + + netlink: Avoid potential NULL-ptr deref parsing set elem expressions + + Since netlink_parse_set_expr() may return NULL, the following deref must + be guarded. + + Fixes: e6d1d0d611958 ("src: add set element multi-statement support") + Signed-off-by: Phil Sutter + +Signed-off-by: Phil Sutter +--- + src/netlink.c | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/src/netlink.c b/src/netlink.c +index bf2cf53..05d5a97 100644 +--- a/src/netlink.c ++++ b/src/netlink.c +@@ -956,7 +956,8 @@ static int set_elem_parse_expressions(struct nftnl_expr *e, void *data) + struct stmt *stmt; + + stmt = netlink_parse_set_expr(set, cache, e); +- list_add_tail(&stmt->list, &setelem_parse_ctx->stmt_list); ++ if (stmt) ++ list_add_tail(&stmt->list, &setelem_parse_ctx->stmt_list); + + return 0; + } diff --git a/0205-tests-shell-check-for-features-not-available-in-5.4.patch b/0205-tests-shell-check-for-features-not-available-in-5.4.patch new file mode 100644 index 0000000..4b5f355 --- /dev/null +++ b/0205-tests-shell-check-for-features-not-available-in-5.4.patch @@ -0,0 +1,90 @@ +From b0b7c18f9dec0cb51513d69c83986ff2dcd384bc Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:14:03 +0200 +Subject: [PATCH] tests: shell: check for features not available in 5.4 + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 883f79de486604e310c5093cfbe4d704794e4825 + +commit 883f79de486604e310c5093cfbe4d704794e4825 +Author: Pablo Neira Ayuso +Date: Thu May 29 11:49:18 2025 +0200 + + tests: shell: check for features not available in 5.4 + + 5.4 -stable kernels report failures in these tests, this kernel version + is lacking these feature. + + The bitshift requirement is needed by this ruleset: + + table ip x { + set s13 { + typeof tcp option mptcp subtype + elements = { mp-join, dss } + } + + chain y { + tcp option mptcp subtype @s13 accept + } + } + + which uses bitshift in its bytecode. + + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + tests/shell/testcases/json/single_flag | 1 + + tests/shell/testcases/sets/elem_limit_0 | 2 +- + tests/shell/testcases/sets/set_stmt | 2 ++ + tests/shell/testcases/sets/typeof_sets_0 | 1 + + 4 files changed, 5 insertions(+), 1 deletion(-) + +diff --git a/tests/shell/testcases/json/single_flag b/tests/shell/testcases/json/single_flag +index 41fab63..43ae452 100755 +--- a/tests/shell/testcases/json/single_flag ++++ b/tests/shell/testcases/json/single_flag +@@ -7,6 +7,7 @@ + # recognized in input (checked against standard syntax input/output) + + # NFT_TEST_REQUIRES(NFT_TEST_HAVE_json) ++# NFT_TEST_REQUIRES(NFT_TEST_HAVE_table_flag_persist) + + set -e + +diff --git a/tests/shell/testcases/sets/elem_limit_0 b/tests/shell/testcases/sets/elem_limit_0 +index b57f927..ed60091 100755 +--- a/tests/shell/testcases/sets/elem_limit_0 ++++ b/tests/shell/testcases/sets/elem_limit_0 +@@ -1,6 +1,6 @@ + #!/bin/bash + +-## requires EXPR ++# NFT_TEST_REQUIRES(NFT_TEST_HAVE_set_expr) + + set -e + +diff --git a/tests/shell/testcases/sets/set_stmt b/tests/shell/testcases/sets/set_stmt +index 0433b67..ea50525 100755 +--- a/tests/shell/testcases/sets/set_stmt ++++ b/tests/shell/testcases/sets/set_stmt +@@ -1,5 +1,7 @@ + #!/bin/bash + ++# NFT_TEST_REQUIRES(NFT_TEST_HAVE_set_expr) ++ + test_set_stmt() { + local i=$1 + local stmt1=$2 +diff --git a/tests/shell/testcases/sets/typeof_sets_0 b/tests/shell/testcases/sets/typeof_sets_0 +index 3a4ed94..baf7c29 100755 +--- a/tests/shell/testcases/sets/typeof_sets_0 ++++ b/tests/shell/testcases/sets/typeof_sets_0 +@@ -5,6 +5,7 @@ + # ways for declaration. + + # NFT_TEST_REQUIRES(NFT_TEST_HAVE_ip_options) ++# NFT_TEST_REQUIRES(NFT_TEST_HAVE_bitshift) + + set -e + diff --git a/0206-tests-py-fix-json-single-flag-output-for-fib-synprox.patch b/0206-tests-py-fix-json-single-flag-output-for-fib-synprox.patch new file mode 100644 index 0000000..4e4dd6d --- /dev/null +++ b/0206-tests-py-fix-json-single-flag-output-for-fib-synprox.patch @@ -0,0 +1,92 @@ +From 8db1ee0117fd65595833d0cc78641e4c5e6f02f3 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:14:04 +0200 +Subject: [PATCH] tests: py: fix json single-flag output for fib & synproxy + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit c2ee1d5978bf6ea10e3b1a74125da5b5e8468f26 + +commit c2ee1d5978bf6ea10e3b1a74125da5b5e8468f26 +Author: Florian Westphal +Date: Mon Jun 2 14:12:16 2025 +0200 + + tests: py: fix json single-flag output for fib & synproxy + + Blamed commits change output format but did not adjust existing tests: + inet/fib.t: WARNING: line 16: '{"nftables": .. + + Fixes: 38f99ee84fe6 ("json: Print single synproxy flags as non-array") + Fixes: dbe5c44f2b89 ("json: Print single fib flag as non-array") + Signed-off-by: Florian Westphal + Acked-by: Phil Sutter + +Signed-off-by: Phil Sutter +--- + tests/py/inet/fib.t.json.output | 32 ++++++++++++++++++++++++++++ + tests/py/inet/synproxy.t.json.output | 17 +++++++++++++++ + 2 files changed, 49 insertions(+) + create mode 100644 tests/py/inet/synproxy.t.json.output + +diff --git a/tests/py/inet/fib.t.json.output b/tests/py/inet/fib.t.json.output +index 52cd46b..e21f1e7 100644 +--- a/tests/py/inet/fib.t.json.output ++++ b/tests/py/inet/fib.t.json.output +@@ -37,3 +37,35 @@ + } + ] + ++# fib daddr oif exists ++[ ++ { ++ "match": { ++ "left": { ++ "fib": { ++ "flags": "daddr", ++ "result": "oif" ++ } ++ }, ++ "op": "==", ++ "right": true ++ } ++ } ++] ++ ++# fib daddr oif missing ++[ ++ { ++ "match": { ++ "left": { ++ "fib": { ++ "flags": "daddr", ++ "result": "oif" ++ } ++ }, ++ "op": "==", ++ "right": false ++ } ++ } ++] ++ +diff --git a/tests/py/inet/synproxy.t.json.output b/tests/py/inet/synproxy.t.json.output +new file mode 100644 +index 0000000..e32cdfb +--- /dev/null ++++ b/tests/py/inet/synproxy.t.json.output +@@ -0,0 +1,17 @@ ++# synproxy timestamp ++[ ++ { ++ "synproxy": { ++ "flags": "timestamp" ++ } ++ } ++] ++ ++# synproxy sack-perm ++[ ++ { ++ "synproxy": { ++ "flags": "sack-perm" ++ } ++ } ++] diff --git a/0207-json-prevent-null-deref-if-chain-policy-is-not-set.patch b/0207-json-prevent-null-deref-if-chain-policy-is-not-set.patch new file mode 100644 index 0000000..452599f --- /dev/null +++ b/0207-json-prevent-null-deref-if-chain-policy-is-not-set.patch @@ -0,0 +1,99 @@ +From bfec7fce47d8647cbc3d0cbd4d2b6de9a8d7f46d Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:14:04 +0200 +Subject: [PATCH] json: prevent null deref if chain->policy is not set + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 69b90023c7220fe283ee38686c758e3494e853d9 + +commit 69b90023c7220fe283ee38686c758e3494e853d9 +Author: Florian Westphal +Date: Mon Jun 2 14:22:33 2025 +0200 + + json: prevent null deref if chain->policy is not set + + The two commits mentioned below resolved null dererence crashes when the + policy resp. priority keyword was missing in the chain/flowtable + specification. + + Same issue exists in the json output path, so apply similar fix there + and extend the existing test cases. + + Fixes: 5b37479b42b3 ("nftables: don't crash in 'list ruleset' if policy is not set") + Fixes: b40bebbcee36 ("rule: do not crash if to-be-printed flowtable lacks priority") + Signed-off-by: Florian Westphal + Acked-by: Phil Sutter + +Signed-off-by: Phil Sutter +--- + src/json.c | 19 ++++++++++++++----- + .../nft-j-f/flowtable-no-priority-crash | 6 ++++++ + .../shell/testcases/nft-f/0021list_ruleset_0 | 7 ++++++- + 3 files changed, 26 insertions(+), 6 deletions(-) + create mode 100644 tests/shell/testcases/bogons/nft-j-f/flowtable-no-priority-crash + +diff --git a/src/json.c b/src/json.c +index 01c5dbc..bbea9aa 100644 +--- a/src/json.c ++++ b/src/json.c +@@ -299,8 +299,14 @@ static json_t *chain_print_json(const struct chain *chain) + if (chain->flags & CHAIN_F_BASECHAIN) { + mpz_export_data(&priority, chain->priority.expr->value, + BYTEORDER_HOST_ENDIAN, sizeof(int)); +- mpz_export_data(&policy, chain->policy->value, +- BYTEORDER_HOST_ENDIAN, sizeof(int)); ++ ++ if (chain->policy) { ++ mpz_export_data(&policy, chain->policy->value, ++ BYTEORDER_HOST_ENDIAN, sizeof(int)); ++ } else { ++ policy = NF_ACCEPT; ++ } ++ + tmp = json_pack("{s:s, s:s, s:i, s:s}", + "type", chain->type.str, + "hook", hooknum2str(chain->handle.family, +@@ -475,10 +481,13 @@ static json_t *obj_print_json(const struct obj *obj) + static json_t *flowtable_print_json(const struct flowtable *ftable) + { + json_t *root, *devs = NULL; +- int i, priority; ++ int i, priority = 0; ++ ++ if (ftable->priority.expr) { ++ mpz_export_data(&priority, ftable->priority.expr->value, ++ BYTEORDER_HOST_ENDIAN, sizeof(int)); ++ } + +- mpz_export_data(&priority, ftable->priority.expr->value, +- BYTEORDER_HOST_ENDIAN, sizeof(int)); + root = json_pack("{s:s, s:s, s:s, s:I, s:s, s:i}", + "family", family2str(ftable->handle.family), + "name", ftable->handle.flowtable.name, +diff --git a/tests/shell/testcases/bogons/nft-j-f/flowtable-no-priority-crash b/tests/shell/testcases/bogons/nft-j-f/flowtable-no-priority-crash +new file mode 100644 +index 0000000..f348da9 +--- /dev/null ++++ b/tests/shell/testcases/bogons/nft-j-f/flowtable-no-priority-crash +@@ -0,0 +1,6 @@ ++table ip filter { ++ flowtable ft1 { ++ devices = { lo } ++ } ++} ++list ruleset +diff --git a/tests/shell/testcases/nft-f/0021list_ruleset_0 b/tests/shell/testcases/nft-f/0021list_ruleset_0 +index 37729b4..f3c3749 100755 +--- a/tests/shell/testcases/nft-f/0021list_ruleset_0 ++++ b/tests/shell/testcases/nft-f/0021list_ruleset_0 +@@ -12,4 +12,9 @@ RULESET="table filter { + list ruleset + " + +-exec $NFT -f - <<< "$RULESET" ++$NFT -f - <<< "$RULESET" ++ ++if [ "$NFT_TEST_HAVE_json" != n ]; then ++ $NFT flush ruleset ++ $NFT -j -f - <<< "$RULESET" ++fi diff --git a/0208-json-work-around-fuzzer-induced-assert-crashes.patch b/0208-json-work-around-fuzzer-induced-assert-crashes.patch new file mode 100644 index 0000000..71845fe --- /dev/null +++ b/0208-json-work-around-fuzzer-induced-assert-crashes.patch @@ -0,0 +1,1045 @@ +From 37ff6f9a918095db3ece832effcec42a66f5e77a Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:14:04 +0200 +Subject: [PATCH] json: work around fuzzer-induced assert crashes + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 18c24d89b9977ddc0900be64fe9e95e7ac1ce896 + +commit 18c24d89b9977ddc0900be64fe9e95e7ac1ce896 +Author: Florian Westphal +Date: Mon Mar 31 16:47:11 2025 +0200 + + json: work around fuzzer-induced assert crashes + + fuzzer can cause assert failures due to json_pack() returning a NULL + value and therefore triggering the assert(out) in __json_pack macro. + + All instances I saw are due to invalid UTF-8 strings, i.e., table/chain + names with non-text characters in them. + + Work around this for now, replace the assert with a plaintext error + message and return NULL instead of abort(). + + Signed-off-by: Florian Westphal + +Signed-off-by: Phil Sutter +--- + src/json.c | 271 +++++++++--------- + .../nft-j-f/Assertion__out_failed_assert | 6 + + 2 files changed, 146 insertions(+), 131 deletions(-) + create mode 100644 tests/shell/testcases/bogons/nft-j-f/Assertion__out_failed_assert + +diff --git a/src/json.c b/src/json.c +index bbea9aa..65fbbfa 100644 +--- a/src/json.c ++++ b/src/json.c +@@ -33,14 +33,23 @@ + #include + #include + +-#ifdef DEBUG +-#define __json_pack json_pack +-#define json_pack(...) ({ \ +- json_t *__out = __json_pack(__VA_ARGS__); \ +- assert(__out); \ +- __out; \ +-}) +-#endif ++static json_t *__nft_json_pack(unsigned int line, const char *fmt, ...) ++{ ++ json_error_t error; ++ json_t *value; ++ va_list ap; ++ ++ va_start(ap, fmt); ++ value = json_vpack_ex(&error, 0, fmt, ap); ++ va_end(ap); ++ ++ if (value) ++ return value; ++ ++ fprintf(stderr, "%s:%d: json_pack failure (%s)\n", __FILE__, line, error.text); ++ return NULL; ++} ++#define nft_json_pack(...) __nft_json_pack(__LINE__, __VA_ARGS__) + + static int json_array_extend_new(json_t *array, json_t *other_array) + { +@@ -84,7 +93,7 @@ static json_t *expr_print_json(const struct expr *expr, struct output_ctx *octx) + fclose(octx->output_fp); + octx->output_fp = fp; + +- return json_pack("s", buf); ++ return nft_json_pack("s", buf); + } + + static json_t *set_dtype_json(const struct expr *key) +@@ -99,7 +108,7 @@ static json_t *set_dtype_json(const struct expr *key) + if (!root) + root = jtok; + else if (json_is_string(root)) +- root = json_pack("[o, o]", root, jtok); ++ root = nft_json_pack("[o, o]", root, jtok); + else + json_array_append_new(root, jtok); + tok = strtok_r(NULL, " .", &tok_safe); +@@ -116,7 +125,7 @@ static json_t *set_key_dtype_json(const struct set *set, + if (!use_typeof) + return set_dtype_json(set->key); + +- return json_pack("{s:o}", "typeof", expr_print_json(set->key, octx)); ++ return nft_json_pack("{s:o}", "typeof", expr_print_json(set->key, octx)); + } + + static json_t *stmt_print_json(const struct stmt *stmt, struct output_ctx *octx) +@@ -138,7 +147,7 @@ static json_t *stmt_print_json(const struct stmt *stmt, struct output_ctx *octx) + fclose(octx->output_fp); + octx->output_fp = fp; + +- return json_pack("s", buf); ++ return nft_json_pack("s", buf); + } + + static json_t *set_stmt_list_json(const struct list_head *stmt_list, +@@ -177,7 +186,7 @@ static json_t *set_print_json(struct output_ctx *octx, const struct set *set) + type = "set"; + } + +- root = json_pack("{s:s, s:s, s:s, s:o, s:I}", ++ root = nft_json_pack("{s:s, s:s, s:s, s:o, s:I}", + "family", family2str(set->handle.family), + "name", set->handle.set.name, + "table", set->handle.table.name, +@@ -191,24 +200,24 @@ static json_t *set_print_json(struct output_ctx *octx, const struct set *set) + + if (!(set->flags & (NFT_SET_CONSTANT))) { + if (set->policy != NFT_SET_POL_PERFORMANCE) { +- tmp = json_pack("s", set_policy2str(set->policy)); ++ tmp = nft_json_pack("s", set_policy2str(set->policy)); + json_object_set_new(root, "policy", tmp); + } + if (set->desc.size) { +- tmp = json_pack("i", set->desc.size); ++ tmp = nft_json_pack("i", set->desc.size); + json_object_set_new(root, "size", tmp); + } + } + + tmp = json_array(); + if (set->flags & NFT_SET_CONSTANT) +- json_array_append_new(tmp, json_pack("s", "constant")); ++ json_array_append_new(tmp, nft_json_pack("s", "constant")); + if (set->flags & NFT_SET_INTERVAL) +- json_array_append_new(tmp, json_pack("s", "interval")); ++ json_array_append_new(tmp, nft_json_pack("s", "interval")); + if (set->flags & NFT_SET_TIMEOUT) +- json_array_append_new(tmp, json_pack("s", "timeout")); ++ json_array_append_new(tmp, nft_json_pack("s", "timeout")); + if (set->flags & NFT_SET_EVAL) +- json_array_append_new(tmp, json_pack("s", "dynamic")); ++ json_array_append_new(tmp, nft_json_pack("s", "dynamic")); + json_add_array_new(root, "flags", tmp); + + if (set->timeout) { +@@ -216,7 +225,7 @@ static json_t *set_print_json(struct output_ctx *octx, const struct set *set) + json_object_set_new(root, "timeout", tmp); + } + if (set->gc_int) { +- tmp = json_pack("i", set->gc_int / 1000); ++ tmp = nft_json_pack("i", set->gc_int / 1000); + json_object_set_new(root, "gc-interval", tmp); + } + if (set->automerge) +@@ -237,7 +246,7 @@ static json_t *set_print_json(struct output_ctx *octx, const struct set *set) + set_stmt_list_json(&set->stmt_list, octx)); + } + +- return json_pack("{s:o}", type, root); ++ return nft_json_pack("{s:o}", type, root); + } + + /* XXX: Merge with set_print_json()? */ +@@ -246,7 +255,7 @@ static json_t *element_print_json(struct output_ctx *octx, + { + json_t *root = expr_print_json(set->init, octx); + +- return json_pack("{s: {s:s, s:s, s:s, s:o}}", "element", ++ return nft_json_pack("{s: {s:s, s:s, s:s, s:o}}", "element", + "family", family2str(set->handle.family), + "table", set->handle.table.name, + "name", set->handle.set.name, +@@ -259,7 +268,7 @@ static json_t *rule_print_json(struct output_ctx *octx, + const struct stmt *stmt; + json_t *root, *tmp; + +- root = json_pack("{s:s, s:s, s:s, s:I}", ++ root = nft_json_pack("{s:s, s:s, s:s, s:I}", + "family", family2str(rule->handle.family), + "table", rule->handle.table.name, + "chain", rule->handle.chain.name, +@@ -279,7 +288,7 @@ static json_t *rule_print_json(struct output_ctx *octx, + json_decref(tmp); + } + +- return json_pack("{s:o}", "rule", root); ++ return nft_json_pack("{s:o}", "rule", root); + } + + static json_t *chain_print_json(const struct chain *chain) +@@ -287,7 +296,7 @@ static json_t *chain_print_json(const struct chain *chain) + json_t *root, *tmp, *devs = NULL; + int priority, policy, i; + +- root = json_pack("{s:s, s:s, s:s, s:I}", ++ root = nft_json_pack("{s:s, s:s, s:s, s:I}", + "family", family2str(chain->handle.family), + "table", chain->handle.table.name, + "name", chain->handle.chain.name, +@@ -307,7 +316,7 @@ static json_t *chain_print_json(const struct chain *chain) + policy = NF_ACCEPT; + } + +- tmp = json_pack("{s:s, s:s, s:i, s:s}", ++ tmp = nft_json_pack("{s:s, s:s, s:i, s:s}", + "type", chain->type.str, + "hook", hooknum2str(chain->handle.family, + chain->hook.num), +@@ -319,7 +328,7 @@ static json_t *chain_print_json(const struct chain *chain) + if (!devs) + devs = json_string(dev); + else if (json_is_string(devs)) +- devs = json_pack("[o, s]", devs, dev); ++ devs = nft_json_pack("[o, s]", devs, dev); + else + json_array_append_new(devs, json_string(dev)); + } +@@ -330,7 +339,7 @@ static json_t *chain_print_json(const struct chain *chain) + json_decref(tmp); + } + +- return json_pack("{s:o}", "chain", root); ++ return nft_json_pack("{s:o}", "chain", root); + } + + static json_t *proto_name_json(uint8_t proto) +@@ -366,28 +375,28 @@ static json_t *obj_print_json(const struct obj *obj) + json_t *root, *tmp, *flags; + uint64_t rate, burst; + +- root = json_pack("{s:s, s:s, s:s, s:I}", ++ root = nft_json_pack("{s:s, s:s, s:s, s:I}", + "family", family2str(obj->handle.family), + "name", obj->handle.obj.name, + "table", obj->handle.table.name, + "handle", obj->handle.handle.id); + + if (obj->comment) { +- tmp = json_pack("{s:s}", "comment", obj->comment); ++ tmp = nft_json_pack("{s:s}", "comment", obj->comment); + json_object_update(root, tmp); + json_decref(tmp); + } + + switch (obj->type) { + case NFT_OBJECT_COUNTER: +- tmp = json_pack("{s:I, s:I}", ++ tmp = nft_json_pack("{s:I, s:I}", + "packets", obj->counter.packets, + "bytes", obj->counter.bytes); + json_object_update(root, tmp); + json_decref(tmp); + break; + case NFT_OBJECT_QUOTA: +- tmp = json_pack("{s:I, s:I, s:b}", ++ tmp = nft_json_pack("{s:I, s:I, s:b}", + "bytes", obj->quota.bytes, + "used", obj->quota.used, + "inv", obj->quota.flags & NFT_QUOTA_F_INV); +@@ -395,13 +404,13 @@ static json_t *obj_print_json(const struct obj *obj) + json_decref(tmp); + break; + case NFT_OBJECT_SECMARK: +- tmp = json_pack("{s:s}", ++ tmp = nft_json_pack("{s:s}", + "context", obj->secmark.ctx); + json_object_update(root, tmp); + json_decref(tmp); + break; + case NFT_OBJECT_CT_HELPER: +- tmp = json_pack("{s:s, s:o, s:s}", ++ tmp = nft_json_pack("{s:s, s:o, s:s}", + "type", obj->ct_helper.name, "protocol", + proto_name_json(obj->ct_helper.l4proto), + "l3proto", family2str(obj->ct_helper.l3proto)); +@@ -411,7 +420,7 @@ static json_t *obj_print_json(const struct obj *obj) + case NFT_OBJECT_CT_TIMEOUT: + tmp = timeout_policy_json(obj->ct_timeout.l4proto, + obj->ct_timeout.timeout); +- tmp = json_pack("{s:o, s:s, s:o}", ++ tmp = nft_json_pack("{s:o, s:s, s:o}", + "protocol", + proto_name_json(obj->ct_timeout.l4proto), + "l3proto", family2str(obj->ct_timeout.l3proto), +@@ -420,7 +429,7 @@ static json_t *obj_print_json(const struct obj *obj) + json_decref(tmp); + break; + case NFT_OBJECT_CT_EXPECT: +- tmp = json_pack("{s:o, s:I, s:I, s:I, s:s}", ++ tmp = nft_json_pack("{s:o, s:I, s:I, s:I, s:s}", + "protocol", + proto_name_json(obj->ct_expect.l4proto), + "dport", obj->ct_expect.dport, +@@ -439,7 +448,7 @@ static json_t *obj_print_json(const struct obj *obj) + burst_unit = get_rate(obj->limit.burst, &burst); + } + +- tmp = json_pack("{s:I, s:s}", ++ tmp = nft_json_pack("{s:I, s:s}", + "rate", rate, + "per", get_unit(obj->limit.unit)); + +@@ -459,9 +468,9 @@ static json_t *obj_print_json(const struct obj *obj) + json_decref(tmp); + break; + case NFT_OBJECT_SYNPROXY: +- tmp = json_pack("{s:i, s:i}", +- "mss", obj->synproxy.mss, +- "wscale", obj->synproxy.wscale); ++ tmp = nft_json_pack("{s:i, s:i}", ++ "mss", obj->synproxy.mss, ++ "wscale", obj->synproxy.wscale); + + flags = json_array(); + if (obj->synproxy.flags & NF_SYNPROXY_OPT_TIMESTAMP) +@@ -475,7 +484,7 @@ static json_t *obj_print_json(const struct obj *obj) + break; + } + +- return json_pack("{s:o}", type, root); ++ return nft_json_pack("{s:o}", type, root); + } + + static json_t *flowtable_print_json(const struct flowtable *ftable) +@@ -488,7 +497,7 @@ static json_t *flowtable_print_json(const struct flowtable *ftable) + BYTEORDER_HOST_ENDIAN, sizeof(int)); + } + +- root = json_pack("{s:s, s:s, s:s, s:I, s:s, s:i}", ++ root = nft_json_pack("{s:s, s:s, s:s, s:I, s:s, s:i}", + "family", family2str(ftable->handle.family), + "name", ftable->handle.flowtable.name, + "table", ftable->handle.table.name, +@@ -501,14 +510,14 @@ static json_t *flowtable_print_json(const struct flowtable *ftable) + if (!devs) + devs = json_string(dev); + else if (json_is_string(devs)) +- devs = json_pack("[o, s]", devs, dev); ++ devs = nft_json_pack("[o, s]", devs, dev); + else + json_array_append_new(devs, json_string(dev)); + } + if (devs) + json_object_set_new(root, "dev", devs); + +- return json_pack("{s:o}", "flowtable", root); ++ return nft_json_pack("{s:o}", "flowtable", root); + } + + static json_t *table_flags_json(const struct table *table) +@@ -532,7 +541,7 @@ static json_t *table_print_json(const struct table *table) + { + json_t *root; + +- root = json_pack("{s:s, s:s, s:I}", ++ root = nft_json_pack("{s:s, s:s, s:I}", + "family", family2str(table->handle.family), + "name", table->handle.table.name, + "handle", table->handle.handle.id); +@@ -541,7 +550,7 @@ static json_t *table_print_json(const struct table *table) + if (table->comment) + json_object_set_new(root, "comment", json_string(table->comment)); + +- return json_pack("{s:o}", "table", root); ++ return nft_json_pack("{s:o}", "table", root); + } + + json_t *flagcmp_expr_json(const struct expr *expr, struct output_ctx *octx) +@@ -576,13 +585,13 @@ __binop_expr_json(int op, const struct expr *expr, struct output_ctx *octx) + + json_t *binop_expr_json(const struct expr *expr, struct output_ctx *octx) + { +- return json_pack("{s:o}", expr_op_symbols[expr->op], ++ return nft_json_pack("{s:o}", expr_op_symbols[expr->op], + __binop_expr_json(expr->op, expr, octx)); + } + + json_t *relational_expr_json(const struct expr *expr, struct output_ctx *octx) + { +- return json_pack("{s:{s:s, s:o, s:o}}", "match", ++ return nft_json_pack("{s:{s:s, s:o, s:o}}", "match", + "op", expr_op_symbols[expr->op] ? : "in", + "left", expr_print_json(expr->left, octx), + "right", expr_print_json(expr->right, octx)); +@@ -595,7 +604,7 @@ json_t *range_expr_json(const struct expr *expr, struct output_ctx *octx) + + octx->flags &= ~NFT_CTX_OUTPUT_SERVICE; + octx->flags |= NFT_CTX_OUTPUT_NUMERIC_PROTO; +- root = json_pack("{s:[o, o]}", "range", ++ root = nft_json_pack("{s:[o, o]}", "range", + expr_print_json(expr->left, octx), + expr_print_json(expr->right, octx)); + octx->flags = flags; +@@ -605,7 +614,7 @@ json_t *range_expr_json(const struct expr *expr, struct output_ctx *octx) + + json_t *meta_expr_json(const struct expr *expr, struct output_ctx *octx) + { +- return json_pack("{s:{s:s}}", "meta", ++ return nft_json_pack("{s:{s:s}}", "meta", + "key", meta_templates[expr->meta.key].token); + } + +@@ -615,23 +624,23 @@ json_t *payload_expr_json(const struct expr *expr, struct output_ctx *octx) + + if (payload_is_known(expr)) { + if (expr->payload.inner_desc) { +- root = json_pack("{s:s, s:s, s:s}", ++ root = nft_json_pack("{s:s, s:s, s:s}", + "tunnel", expr->payload.inner_desc->name, + "protocol", expr->payload.desc->name, + "field", expr->payload.tmpl->token); + } else { +- root = json_pack("{s:s, s:s}", ++ root = nft_json_pack("{s:s, s:s}", + "protocol", expr->payload.desc->name, + "field", expr->payload.tmpl->token); + } + } else { +- root = json_pack("{s:s, s:i, s:i}", ++ root = nft_json_pack("{s:s, s:i, s:i}", + "base", proto_base_tokens[expr->payload.base], + "offset", expr->payload.offset, + "len", expr->len); + } + +- return json_pack("{s:o}", "payload", root); ++ return nft_json_pack("{s:o}", "payload", root); + } + + json_t *ct_expr_json(const struct expr *expr, struct output_ctx *octx) +@@ -640,7 +649,7 @@ json_t *ct_expr_json(const struct expr *expr, struct output_ctx *octx) + enum nft_ct_keys key = expr->ct.key; + json_t *root; + +- root = json_pack("{s:s}", "key", ct_templates[key].token); ++ root = nft_json_pack("{s:s}", "key", ct_templates[key].token); + + if (expr->ct.direction < 0) + goto out; +@@ -648,7 +657,7 @@ json_t *ct_expr_json(const struct expr *expr, struct output_ctx *octx) + if (dirstr) + json_object_set_new(root, "dir", json_string(dirstr)); + out: +- return json_pack("{s:o}", "ct", root); ++ return nft_json_pack("{s:o}", "ct", root); + } + + json_t *concat_expr_json(const struct expr *expr, struct output_ctx *octx) +@@ -659,7 +668,7 @@ json_t *concat_expr_json(const struct expr *expr, struct output_ctx *octx) + list_for_each_entry(i, &expr->expressions, list) + json_array_append_new(array, expr_print_json(i, octx)); + +- return json_pack("{s:o}", "concat", array); ++ return nft_json_pack("{s:o}", "concat", array); + } + + json_t *set_expr_json(const struct expr *expr, struct output_ctx *octx) +@@ -670,7 +679,7 @@ json_t *set_expr_json(const struct expr *expr, struct output_ctx *octx) + list_for_each_entry(i, &expr->expressions, list) + json_array_append_new(array, expr_print_json(i, octx)); + +- return json_pack("{s:o}", "set", array); ++ return nft_json_pack("{s:o}", "set", array); + } + + json_t *set_ref_expr_json(const struct expr *expr, struct output_ctx *octx) +@@ -678,7 +687,7 @@ json_t *set_ref_expr_json(const struct expr *expr, struct output_ctx *octx) + if (set_is_anonymous(expr->set->flags)) { + return expr_print_json(expr->set->init, octx); + } else { +- return json_pack("s+", "@", expr->set->handle.set.name); ++ return nft_json_pack("s+", "@", expr->set->handle.set.name); + } + } + +@@ -694,7 +703,7 @@ json_t *set_elem_expr_json(const struct expr *expr, struct output_ctx *octx) + /* these element attributes require formal set elem syntax */ + if (expr->timeout || expr->expiration || expr->comment || + !list_empty(&expr->stmt_list)) { +- root = json_pack("{s:o}", "val", root); ++ root = nft_json_pack("{s:o}", "val", root); + + if (expr->timeout) { + tmp = json_integer(expr->timeout / 1000); +@@ -716,7 +725,7 @@ json_t *set_elem_expr_json(const struct expr *expr, struct output_ctx *octx) + /* TODO: only one statement per element. */ + break; + } +- return json_pack("{s:o}", "elem", root); ++ return nft_json_pack("{s:o}", "elem", root); + } + + return root; +@@ -726,7 +735,7 @@ json_t *prefix_expr_json(const struct expr *expr, struct output_ctx *octx) + { + json_t *root = expr_print_json(expr->prefix, octx); + +- return json_pack("{s:{s:o, s:i}}", "prefix", ++ return nft_json_pack("{s:{s:o, s:i}}", "prefix", + "addr", root, + "len", expr->prefix_len); + } +@@ -739,7 +748,7 @@ json_t *list_expr_json(const struct expr *expr, struct output_ctx *octx) + list_for_each_entry(i, &expr->expressions, list) + json_array_append_new(array, expr_print_json(i, octx)); + +- //return json_pack("{s:s, s:o}", "type", "list", "val", array); ++ //return nft_json_pack("{s:s, s:o}", "type", "list", "val", array); + return array; + } + +@@ -750,7 +759,7 @@ json_t *unary_expr_json(const struct expr *expr, struct output_ctx *octx) + + json_t *mapping_expr_json(const struct expr *expr, struct output_ctx *octx) + { +- return json_pack("[o, o]", ++ return nft_json_pack("[o, o]", + expr_print_json(expr->left, octx), + expr_print_json(expr->right, octx)); + } +@@ -763,7 +772,7 @@ json_t *map_expr_json(const struct expr *expr, struct output_ctx *octx) + expr->mappings->set->data->dtype->type == TYPE_VERDICT) + type = "vmap"; + +- return json_pack("{s:{s:o, s:o}}", type, ++ return nft_json_pack("{s:{s:o, s:o}}", type, + "key", expr_print_json(expr->map, octx), + "data", expr_print_json(expr->mappings, octx)); + } +@@ -785,36 +794,36 @@ json_t *exthdr_expr_json(const struct expr *expr, struct output_ctx *octx) + if (offset < 4) + offstr = offstrs[offset]; + +- root = json_pack("{s:s+}", "name", desc, offstr); ++ root = nft_json_pack("{s:s+}", "name", desc, offstr); + + if (!is_exists) + json_object_set_new(root, "field", json_string(field)); + } else { +- root = json_pack("{s:i, s:i, s:i}", ++ root = nft_json_pack("{s:i, s:i, s:i}", + "base", expr->exthdr.raw_type, + "offset", expr->exthdr.offset, + "len", expr->len); + } + +- return json_pack("{s:o}", "tcp option", root); ++ return nft_json_pack("{s:o}", "tcp option", root); + } + + if (expr->exthdr.op == NFT_EXTHDR_OP_DCCP) { +- root = json_pack("{s:i}", "type", expr->exthdr.raw_type); +- return json_pack("{s:o}", "dccp option", root); ++ root = nft_json_pack("{s:i}", "type", expr->exthdr.raw_type); ++ return nft_json_pack("{s:o}", "dccp option", root); + } + +- root = json_pack("{s:s}", "name", desc); ++ root = nft_json_pack("{s:s}", "name", desc); + if (!is_exists) + json_object_set_new(root, "field", json_string(field)); + + switch (expr->exthdr.op) { + case NFT_EXTHDR_OP_IPV4: +- return json_pack("{s:o}", "ip option", root); ++ return nft_json_pack("{s:o}", "ip option", root); + case NFT_EXTHDR_OP_SCTP: +- return json_pack("{s:o}", "sctp chunk", root); ++ return nft_json_pack("{s:o}", "sctp chunk", root); + default: +- return json_pack("{s:o}", "exthdr", root); ++ return nft_json_pack("{s:o}", "exthdr", root); + } + } + +@@ -851,15 +860,15 @@ json_t *verdict_expr_json(const struct expr *expr, struct output_ctx *octx) + return NULL; + } + if (chain) +- return json_pack("{s:{s:o}}", name, "target", chain); ++ return nft_json_pack("{s:{s:o}}", name, "target", chain); + else +- return json_pack("{s:n}", name); ++ return nft_json_pack("{s:n}", name); + } + + json_t *rt_expr_json(const struct expr *expr, struct output_ctx *octx) + { + const char *key = rt_templates[expr->rt.key].token; +- json_t *root = json_pack("{s:s}", "key", key); ++ json_t *root = nft_json_pack("{s:s}", "key", key); + const char *family = NULL; + + switch (expr->rt.key) { +@@ -876,7 +885,7 @@ json_t *rt_expr_json(const struct expr *expr, struct output_ctx *octx) + if (family) + json_object_set_new(root, "family", json_string(family)); + +- return json_pack("{s:o}", "rt", root); ++ return nft_json_pack("{s:o}", "rt", root); + } + + json_t *numgen_expr_json(const struct expr *expr, struct output_ctx *octx) +@@ -895,7 +904,7 @@ json_t *numgen_expr_json(const struct expr *expr, struct output_ctx *octx) + break; + } + +- return json_pack("{s:{s:s, s:i, s:i}}", "numgen", ++ return nft_json_pack("{s:{s:s, s:i, s:i}}", "numgen", + "mode", mode, + "mod", expr->numgen.mod, + "offset", expr->numgen.offset); +@@ -917,7 +926,7 @@ json_t *hash_expr_json(const struct expr *expr, struct output_ctx *octx) + break; + } + +- root = json_pack("{s:i}", "mod", expr->hash.mod); ++ root = nft_json_pack("{s:i}", "mod", expr->hash.mod); + if (expr->hash.seed_set) + json_object_set_new(root, "seed", + json_integer(expr->hash.seed)); +@@ -927,7 +936,7 @@ json_t *hash_expr_json(const struct expr *expr, struct output_ctx *octx) + if (jexpr) + json_object_set_new(root, "expr", jexpr); + +- return json_pack("{s:o}", type, root); ++ return nft_json_pack("{s:o}", type, root); + } + + json_t *fib_expr_json(const struct expr *expr, struct output_ctx *octx) +@@ -936,7 +945,7 @@ json_t *fib_expr_json(const struct expr *expr, struct output_ctx *octx) + unsigned int flags = expr->fib.flags & ~NFTA_FIB_F_PRESENT; + json_t *root; + +- root = json_pack("{s:s}", "result", fib_result_str(expr->fib.result)); ++ root = nft_json_pack("{s:s}", "result", fib_result_str(expr->fib.result)); + + if (flags) { + json_t *tmp = json_array(); +@@ -953,7 +962,7 @@ json_t *fib_expr_json(const struct expr *expr, struct output_ctx *octx) + + json_add_array_new(root, "flags", tmp); + } +- return json_pack("{s:o}", "fib", root); ++ return nft_json_pack("{s:o}", "fib", root); + } + + static json_t *symbolic_constant_json(const struct symbol_table *tbl, +@@ -1026,7 +1035,7 @@ json_t *constant_expr_json(const struct expr *expr, struct output_ctx *octx) + + json_t *socket_expr_json(const struct expr *expr, struct output_ctx *octx) + { +- return json_pack("{s:{s:s}}", "socket", "key", ++ return nft_json_pack("{s:{s:s}}", "socket", "key", + socket_templates[expr->socket.key].token); + } + +@@ -1035,9 +1044,9 @@ json_t *osf_expr_json(const struct expr *expr, struct output_ctx *octx) + json_t *root; + + if (expr->osf.flags & NFT_OSF_F_VERSION) +- root = json_pack("{s:s}", "key", "version"); ++ root = nft_json_pack("{s:s}", "key", "version"); + else +- root = json_pack("{s:s}", "key", "name"); ++ root = nft_json_pack("{s:s}", "key", "name"); + + switch (expr->osf.ttl) { + case 1: +@@ -1048,7 +1057,7 @@ json_t *osf_expr_json(const struct expr *expr, struct output_ctx *octx) + break; + } + +- return json_pack("{s:o}", "osf", root); ++ return nft_json_pack("{s:o}", "osf", root); + } + + json_t *xfrm_expr_json(const struct expr *expr, struct output_ctx *octx) +@@ -1085,7 +1094,7 @@ json_t *xfrm_expr_json(const struct expr *expr, struct output_ctx *octx) + break; + } + +- root = json_pack("{s:s}", "key", name); ++ root = nft_json_pack("{s:s}", "key", name); + + if (family) + json_object_set_new(root, "family", json_string(family)); +@@ -1093,7 +1102,7 @@ json_t *xfrm_expr_json(const struct expr *expr, struct output_ctx *octx) + json_object_set_new(root, "dir", json_string(dirstr)); + json_object_set_new(root, "spnum", json_integer(expr->xfrm.spnum)); + +- return json_pack("{s:o}", "ipsec", root); ++ return nft_json_pack("{s:o}", "ipsec", root); + } + + json_t *integer_type_json(const struct expr *expr, struct output_ctx *octx) +@@ -1215,21 +1224,21 @@ json_t *expr_stmt_json(const struct stmt *stmt, struct output_ctx *octx) + + json_t *flow_offload_stmt_json(const struct stmt *stmt, struct output_ctx *octx) + { +- return json_pack("{s:{s:s, s:s+}}", "flow", ++ return nft_json_pack("{s:{s:s, s:s+}}", "flow", + "op", "add", "flowtable", + "@", stmt->flow.table_name); + } + + json_t *payload_stmt_json(const struct stmt *stmt, struct output_ctx *octx) + { +- return json_pack("{s: {s:o, s:o}}", "mangle", ++ return nft_json_pack("{s: {s:o, s:o}}", "mangle", + "key", expr_print_json(stmt->payload.expr, octx), + "value", expr_print_json(stmt->payload.val, octx)); + } + + json_t *exthdr_stmt_json(const struct stmt *stmt, struct output_ctx *octx) + { +- return json_pack("{s: {s:o, s:o}}", "mangle", ++ return nft_json_pack("{s: {s:o, s:o}}", "mangle", + "key", expr_print_json(stmt->exthdr.expr, octx), + "value", expr_print_json(stmt->exthdr.val, octx)); + } +@@ -1241,7 +1250,7 @@ json_t *quota_stmt_json(const struct stmt *stmt, struct output_ctx *octx) + json_t *root; + + data_unit = get_rate(stmt->quota.bytes, &bytes); +- root = json_pack("{s:I, s:s}", ++ root = nft_json_pack("{s:I, s:s}", + "val", bytes, + "val_unit", data_unit); + +@@ -1253,7 +1262,7 @@ json_t *quota_stmt_json(const struct stmt *stmt, struct output_ctx *octx) + json_object_set_new(root, "used_unit", json_string(data_unit)); + } + +- return json_pack("{s:o}", "quota", root); ++ return nft_json_pack("{s:o}", "quota", root); + } + + json_t *ct_stmt_json(const struct stmt *stmt, struct output_ctx *octx) +@@ -1266,7 +1275,7 @@ json_t *ct_stmt_json(const struct stmt *stmt, struct output_ctx *octx) + }, + }; + +- return json_pack("{s:{s:o, s:o}}", "mangle", ++ return nft_json_pack("{s:{s:o, s:o}}", "mangle", + "key", ct_expr_json(&expr, octx), + "value", expr_print_json(stmt->ct.expr, octx)); + } +@@ -1284,7 +1293,7 @@ json_t *limit_stmt_json(const struct stmt *stmt, struct output_ctx *octx) + burst_unit = get_rate(stmt->limit.burst, &burst); + } + +- root = json_pack("{s:I, s:I, s:s}", ++ root = nft_json_pack("{s:I, s:I, s:s}", + "rate", rate, + "burst", burst, + "per", get_unit(stmt->limit.unit)); +@@ -1296,14 +1305,14 @@ json_t *limit_stmt_json(const struct stmt *stmt, struct output_ctx *octx) + json_object_set_new(root, "burst_unit", + json_string(burst_unit)); + +- return json_pack("{s:o}", "limit", root); ++ return nft_json_pack("{s:o}", "limit", root); + } + + json_t *fwd_stmt_json(const struct stmt *stmt, struct output_ctx *octx) + { + json_t *root, *tmp; + +- root = json_pack("{s:o}", "dev", expr_print_json(stmt->fwd.dev, octx)); ++ root = nft_json_pack("{s:o}", "dev", expr_print_json(stmt->fwd.dev, octx)); + + if (stmt->fwd.addr) { + tmp = json_string(family2str(stmt->fwd.family)); +@@ -1313,12 +1322,12 @@ json_t *fwd_stmt_json(const struct stmt *stmt, struct output_ctx *octx) + json_object_set_new(root, "addr", tmp); + } + +- return json_pack("{s:o}", "fwd", root); ++ return nft_json_pack("{s:o}", "fwd", root); + } + + json_t *notrack_stmt_json(const struct stmt *stmt, struct output_ctx *octx) + { +- return json_pack("{s:n}", "notrack"); ++ return nft_json_pack("{s:n}", "notrack"); + } + + json_t *dup_stmt_json(const struct stmt *stmt, struct output_ctx *octx) +@@ -1326,27 +1335,27 @@ json_t *dup_stmt_json(const struct stmt *stmt, struct output_ctx *octx) + json_t *root; + + if (stmt->dup.to) { +- root = json_pack("{s:o}", "addr", expr_print_json(stmt->dup.to, octx)); ++ root = nft_json_pack("{s:o}", "addr", expr_print_json(stmt->dup.to, octx)); + if (stmt->dup.dev) + json_object_set_new(root, "dev", + expr_print_json(stmt->dup.dev, octx)); + } else { + root = json_null(); + } +- return json_pack("{s:o}", "dup", root); ++ return nft_json_pack("{s:o}", "dup", root); + } + + json_t *meta_stmt_json(const struct stmt *stmt, struct output_ctx *octx) + { + json_t *root; + +- root = json_pack("{s:{s:s}}", "meta", ++ root = nft_json_pack("{s:{s:s}}", "meta", + "key", meta_templates[stmt->meta.key].token); +- root = json_pack("{s:o, s:o}", ++ root = nft_json_pack("{s:o, s:o}", + "key", root, + "value", expr_print_json(stmt->meta.expr, octx)); + +- return json_pack("{s:o}", "mangle", root); ++ return nft_json_pack("{s:o}", "mangle", root); + } + + json_t *log_stmt_json(const struct stmt *stmt, struct output_ctx *octx) +@@ -1395,7 +1404,7 @@ json_t *log_stmt_json(const struct stmt *stmt, struct output_ctx *octx) + root = json_null(); + } + +- return json_pack("{s:o}", "log", root); ++ return nft_json_pack("{s:o}", "log", root); + } + + static json_t *nat_flags_json(uint32_t flags) +@@ -1457,7 +1466,7 @@ json_t *nat_stmt_json(const struct stmt *stmt, struct output_ctx *octx) + root = json_null(); + } + +- return json_pack("{s:o}", nat_etype2str(stmt->nat.type), root); ++ return nft_json_pack("{s:o}", nat_etype2str(stmt->nat.type), root); + } + + json_t *reject_stmt_json(const struct stmt *stmt, struct output_ctx *octx) +@@ -1487,7 +1496,7 @@ json_t *reject_stmt_json(const struct stmt *stmt, struct output_ctx *octx) + } + + if (!type && !jexpr) +- return json_pack("{s:n}", "reject"); ++ return nft_json_pack("{s:n}", "reject"); + + root = json_object(); + if (type) +@@ -1495,15 +1504,15 @@ json_t *reject_stmt_json(const struct stmt *stmt, struct output_ctx *octx) + if (jexpr) + json_object_set_new(root, "expr", jexpr); + +- return json_pack("{s:o}", "reject", root); ++ return nft_json_pack("{s:o}", "reject", root); + } + + json_t *counter_stmt_json(const struct stmt *stmt, struct output_ctx *octx) + { + if (nft_output_stateless(octx)) +- return json_pack("{s:n}", "counter"); ++ return nft_json_pack("{s:n}", "counter"); + +- return json_pack("{s:{s:I, s:I}}", "counter", ++ return nft_json_pack("{s:{s:I, s:I}}", "counter", + "packets", stmt->counter.packets, + "bytes", stmt->counter.bytes); + } +@@ -1511,16 +1520,16 @@ json_t *counter_stmt_json(const struct stmt *stmt, struct output_ctx *octx) + json_t *last_stmt_json(const struct stmt *stmt, struct output_ctx *octx) + { + if (nft_output_stateless(octx) || stmt->last.set == 0) +- return json_pack("{s:n}", "last"); ++ return nft_json_pack("{s:n}", "last"); + +- return json_pack("{s:{s:I}}", "last", "used", stmt->last.used); ++ return nft_json_pack("{s:{s:I}}", "last", "used", stmt->last.used); + } + + json_t *set_stmt_json(const struct stmt *stmt, struct output_ctx *octx) + { + json_t *root; + +- root = json_pack("{s:s, s:o, s:s+}", ++ root = nft_json_pack("{s:s, s:o, s:s+}", + "op", set_stmt_op_names[stmt->set.op], + "elem", expr_print_json(stmt->set.key, octx), + "set", "@", stmt->set.set->set->handle.set.name); +@@ -1531,14 +1540,14 @@ json_t *set_stmt_json(const struct stmt *stmt, struct output_ctx *octx) + octx)); + } + +- return json_pack("{s:o}", "set", root); ++ return nft_json_pack("{s:o}", "set", root); + } + + json_t *map_stmt_json(const struct stmt *stmt, struct output_ctx *octx) + { + json_t *root; + +- root = json_pack("{s:s, s:o, s:o, s:s+}", ++ root = nft_json_pack("{s:s, s:o, s:o, s:s+}", + "op", set_stmt_op_names[stmt->map.op], + "elem", expr_print_json(stmt->map.key, octx), + "data", expr_print_json(stmt->map.data, octx), +@@ -1550,7 +1559,7 @@ json_t *map_stmt_json(const struct stmt *stmt, struct output_ctx *octx) + octx)); + } + +- return json_pack("{s:o}", "map", root); ++ return nft_json_pack("{s:o}", "map", root); + } + + json_t *objref_stmt_json(const struct stmt *stmt, struct output_ctx *octx) +@@ -1562,7 +1571,7 @@ json_t *objref_stmt_json(const struct stmt *stmt, struct output_ctx *octx) + else + name = objref_type_name(stmt->objref.type); + +- return json_pack("{s:o}", name, expr_print_json(stmt->objref.expr, octx)); ++ return nft_json_pack("{s:o}", name, expr_print_json(stmt->objref.expr, octx)); + } + + json_t *meter_stmt_json(const struct stmt *stmt, struct output_ctx *octx) +@@ -1574,7 +1583,7 @@ json_t *meter_stmt_json(const struct stmt *stmt, struct output_ctx *octx) + tmp = stmt_print_json(stmt->meter.stmt, octx); + octx->flags = flags; + +- root = json_pack("{s:o, s:o, s:i}", ++ root = nft_json_pack("{s:o, s:o, s:i}", + "key", expr_print_json(stmt->meter.key, octx), + "stmt", tmp, + "size", stmt->meter.size); +@@ -1583,7 +1592,7 @@ json_t *meter_stmt_json(const struct stmt *stmt, struct output_ctx *octx) + json_object_set_new(root, "name", tmp); + } + +- return json_pack("{s:o}", "meter", root); ++ return nft_json_pack("{s:o}", "meter", root); + } + + json_t *queue_stmt_json(const struct stmt *stmt, struct output_ctx *octx) +@@ -1608,7 +1617,7 @@ json_t *queue_stmt_json(const struct stmt *stmt, struct output_ctx *octx) + root = json_null(); + } + +- return json_pack("{s:o}", "queue", root); ++ return nft_json_pack("{s:o}", "queue", root); + } + + json_t *verdict_stmt_json(const struct stmt *stmt, struct output_ctx *octx) +@@ -1618,12 +1627,12 @@ json_t *verdict_stmt_json(const struct stmt *stmt, struct output_ctx *octx) + + json_t *connlimit_stmt_json(const struct stmt *stmt, struct output_ctx *octx) + { +- json_t *root = json_pack("{s:i}", "val", stmt->connlimit.count); ++ json_t *root = nft_json_pack("{s:i}", "val", stmt->connlimit.count); + + if (stmt->connlimit.flags & NFT_CONNLIMIT_F_INV) + json_object_set_new(root, "inv", json_true()); + +- return json_pack("{s:o}", "ct count", root); ++ return nft_json_pack("{s:o}", "ct count", root); + } + + json_t *tproxy_stmt_json(const struct stmt *stmt, struct output_ctx *octx) +@@ -1646,7 +1655,7 @@ json_t *tproxy_stmt_json(const struct stmt *stmt, struct output_ctx *octx) + json_object_set_new(root, "port", tmp); + } + +- return json_pack("{s:o}", "tproxy", root); ++ return nft_json_pack("{s:o}", "tproxy", root); + } + + json_t *synproxy_stmt_json(const struct stmt *stmt, struct output_ctx *octx) +@@ -1671,12 +1680,12 @@ json_t *synproxy_stmt_json(const struct stmt *stmt, struct output_ctx *octx) + root = json_null(); + } + +- return json_pack("{s:o}", "synproxy", root); ++ return nft_json_pack("{s:o}", "synproxy", root); + } + + json_t *optstrip_stmt_json(const struct stmt *stmt, struct output_ctx *octx) + { +- return json_pack("{s:o}", "reset", ++ return nft_json_pack("{s:o}", "reset", + expr_print_json(stmt->optstrip.expr, octx)); + } + +@@ -1688,7 +1697,7 @@ json_t *xt_stmt_json(const struct stmt *stmt, struct output_ctx *octx) + [NFT_XT_WATCHER] = "watcher", + }; + +- return json_pack("{s:{s:s, s:s}}", "xt", ++ return nft_json_pack("{s:{s:s, s:s}}", "xt", + "type", xt_typename[stmt->xt.type], + "name", stmt->xt.name); + } +@@ -1831,7 +1840,7 @@ static json_t *do_list_set_json(struct netlink_ctx *ctx, + return json_null(); + } + +- return json_pack("[o]", set_print_json(&ctx->nft->output, set)); ++ return nft_json_pack("[o]", set_print_json(&ctx->nft->output, set)); + } + + static json_t *do_list_sets_json(struct netlink_ctx *ctx, struct cmd *cmd) +@@ -1929,7 +1938,7 @@ static json_t *do_list_flowtables_json(struct netlink_ctx *ctx, struct cmd *cmd) + + static json_t *generate_json_metainfo(void) + { +- return json_pack("{s: {s:s, s:s, s:i}}", "metainfo", ++ return nft_json_pack("{s: {s:s, s:s, s:i}}", "metainfo", + "version", PACKAGE_VERSION, + "release_name", RELEASE_NAME, + "json_schema_version", JSON_SCHEMA_VERSION); +@@ -2042,7 +2051,7 @@ int do_command_list_json(struct netlink_ctx *ctx, struct cmd *cmd) + + json_array_insert_new(root, 0, generate_json_metainfo()); + +- root = json_pack("{s:o}", "nftables", root); ++ root = nft_json_pack("{s:o}", "nftables", root); + json_dumpf(root, ctx->nft->output.output_fp, 0); + json_decref(root); + fprintf(ctx->nft->output.output_fp, "\n"); +@@ -2055,7 +2064,7 @@ static void monitor_print_json(struct netlink_mon_handler *monh, + { + struct nft_ctx *nft = monh->ctx->nft; + +- obj = json_pack("{s:o}", cmd, obj); ++ obj = nft_json_pack("{s:o}", cmd, obj); + if (nft_output_echo(&nft->output) && !nft->json_root) { + json_array_append_new(nft->json_echo, obj); + } else { +diff --git a/tests/shell/testcases/bogons/nft-j-f/Assertion__out_failed_assert b/tests/shell/testcases/bogons/nft-j-f/Assertion__out_failed_assert +new file mode 100644 +index 0000000..f8ce089 +--- /dev/null ++++ b/tests/shell/testcases/bogons/nft-j-f/Assertion__out_failed_assert +@@ -0,0 +1,6 @@ ++table ip test-ip { ++ quota htquota { comment "tŽst5" 5 kbytes ++ } ++} ++list ruleset ++add rule t c counter diff --git a/0209-netlink-Fix-for-potential-crash-parsing-a-flowtable.patch b/0209-netlink-Fix-for-potential-crash-parsing-a-flowtable.patch new file mode 100644 index 0000000..8ae14c3 --- /dev/null +++ b/0209-netlink-Fix-for-potential-crash-parsing-a-flowtable.patch @@ -0,0 +1,40 @@ +From a5b99f520504938300319cc466b6d8e01fda316c Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:14:04 +0200 +Subject: [PATCH] netlink: Fix for potential crash parsing a flowtable + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit d5ef04441eb1de3efc27aa70193fe3d7f0b5c408 + +commit d5ef04441eb1de3efc27aa70193fe3d7f0b5c408 +Author: Phil Sutter +Date: Wed Jun 11 13:12:56 2025 +0200 + + netlink: Fix for potential crash parsing a flowtable + + Kernel's flowtable message might not contain the + NFTA_FLOWTABLE_HOOK_DEVS attribute. In that case, nftnl_flowtable_get() + will return NULL for the respective nftnl attribute. + + Fixes: db0697ce7f602 ("src: support for flowtable listing") + Signed-off-by: Phil Sutter + Reviewed-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/netlink.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/netlink.c b/src/netlink.c +index 05d5a97..1de4642 100644 +--- a/src/netlink.c ++++ b/src/netlink.c +@@ -1812,7 +1812,7 @@ netlink_delinearize_flowtable(struct netlink_ctx *ctx, + if (nftnl_flowtable_is_set(nlo, NFTNL_FLOWTABLE_FLAGS)) + flowtable->flags = nftnl_flowtable_get_u32(nlo, NFTNL_FLOWTABLE_FLAGS); + dev_array = nftnl_flowtable_get(nlo, NFTNL_FLOWTABLE_DEVICES); +- while (dev_array[len]) ++ while (dev_array && dev_array[len]) + len++; + + if (len) diff --git a/0210-netlink-Do-not-allocate-a-bogus-flowtable-priority-e.patch b/0210-netlink-Do-not-allocate-a-bogus-flowtable-priority-e.patch new file mode 100644 index 0000000..d280c12 --- /dev/null +++ b/0210-netlink-Do-not-allocate-a-bogus-flowtable-priority-e.patch @@ -0,0 +1,49 @@ +From 9f69d896588779ce915492e72ea6582cf0168792 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:14:04 +0200 +Subject: [PATCH] netlink: Do not allocate a bogus flowtable priority expr + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 10b9a85b3278e0933bf47226588fede8c9fcbcc8 + +commit 10b9a85b3278e0933bf47226588fede8c9fcbcc8 +Author: Phil Sutter +Date: Wed Jun 11 14:15:38 2025 +0200 + + netlink: Do not allocate a bogus flowtable priority expr + + Code accidentally treats missing NFTNL_FLOWTABLE_PRIO attribute as zero + prio value which may not be correct. + + Fixes: db0697ce7f602 ("src: support for flowtable listing") + Signed-off-by: Phil Sutter + Reviewed-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/netlink.c | 6 ++++-- + 1 file changed, 4 insertions(+), 2 deletions(-) + +diff --git a/src/netlink.c b/src/netlink.c +index 1de4642..df7ce21 100644 +--- a/src/netlink.c ++++ b/src/netlink.c +@@ -1827,14 +1827,16 @@ netlink_delinearize_flowtable(struct netlink_ctx *ctx, + sizeof(char *), qsort_device_cmp); + } + +- priority = nftnl_flowtable_get_u32(nlo, NFTNL_FLOWTABLE_PRIO); +- flowtable->priority.expr = ++ if (nftnl_flowtable_is_set(nlo, NFTNL_FLOWTABLE_PRIO)) { ++ priority = nftnl_flowtable_get_u32(nlo, NFTNL_FLOWTABLE_PRIO); ++ flowtable->priority.expr = + constant_expr_alloc(&netlink_location, + &integer_type, + BYTEORDER_HOST_ENDIAN, + sizeof(int) * + BITS_PER_BYTE, + &priority); ++ } + flowtable->hook.num = + nftnl_flowtable_get_u32(nlo, NFTNL_FLOWTABLE_HOOKNUM); + flowtable->flags = diff --git a/0211-tests-monitor-Fix-for-single-flag-array-avoidance.patch b/0211-tests-monitor-Fix-for-single-flag-array-avoidance.patch new file mode 100644 index 0000000..6492e92 --- /dev/null +++ b/0211-tests-monitor-Fix-for-single-flag-array-avoidance.patch @@ -0,0 +1,120 @@ +From d1f234e701fe4b9eb1f5af80469f22b17e0e129e Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:14:04 +0200 +Subject: [PATCH] tests: monitor: Fix for single flag array avoidance + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit b5f43dfc8b28989290a16818f00ca73c6cd8ff65 + +commit b5f43dfc8b28989290a16818f00ca73c6cd8ff65 +Author: Phil Sutter +Date: Wed Jun 11 17:15:22 2025 +0200 + + tests: monitor: Fix for single flag array avoidance + + Missed to update the JSON monitor expected output. + + Fixes: 6bedb12af1658 ("json: Print single set flag as non-array") + Signed-off-by: Phil Sutter + Reviewed-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + tests/monitor/testcases/map-expr.t | 2 +- + tests/monitor/testcases/set-concat-interval.t | 2 +- + tests/monitor/testcases/set-interval.t | 2 +- + tests/monitor/testcases/set-maps.t | 2 +- + tests/monitor/testcases/set-mixed.t | 2 +- + tests/monitor/testcases/set-multiple.t | 4 ++-- + tests/monitor/testcases/set-simple.t | 2 +- + 7 files changed, 8 insertions(+), 8 deletions(-) + +diff --git a/tests/monitor/testcases/map-expr.t b/tests/monitor/testcases/map-expr.t +index d11ad0e..9042004 100644 +--- a/tests/monitor/testcases/map-expr.t ++++ b/tests/monitor/testcases/map-expr.t +@@ -3,4 +3,4 @@ I add table ip t + I add map ip t m { typeof meta day . meta hour : verdict; flags interval; counter; } + O - + J {"add": {"table": {"family": "ip", "name": "t", "handle": 0}}} +-J {"add": {"map": {"family": "ip", "name": "m", "table": "t", "type": {"typeof": {"concat": [{"meta": {"key": "day"}}, {"meta": {"key": "hour"}}]}}, "handle": 0, "map": "verdict", "flags": ["interval"], "stmt": [{"counter": null}]}}} ++J {"add": {"map": {"family": "ip", "name": "m", "table": "t", "type": {"typeof": {"concat": [{"meta": {"key": "day"}}, {"meta": {"key": "hour"}}]}}, "handle": 0, "map": "verdict", "flags": "interval", "stmt": [{"counter": null}]}}} +diff --git a/tests/monitor/testcases/set-concat-interval.t b/tests/monitor/testcases/set-concat-interval.t +index 3542b82..a42682f 100644 +--- a/tests/monitor/testcases/set-concat-interval.t ++++ b/tests/monitor/testcases/set-concat-interval.t +@@ -10,6 +10,6 @@ I add map ip t s { typeof udp length . @ih,32,32 : verdict; flags interval; elem + O add map ip t s { typeof udp length . @ih,32,32 : verdict; flags interval; } + O add element ip t s { 20-80 . 0x14 : accept } + O add element ip t s { 1-10 . 0xa : drop } +-J {"add": {"map": {"family": "ip", "name": "s", "table": "t", "type": {"typeof": {"concat": [{"payload": {"protocol": "udp", "field": "length"}}, {"payload": {"base": "ih", "offset": 32, "len": 32}}]}}, "handle": 0, "map": "verdict", "flags": ["interval"]}}} ++J {"add": {"map": {"family": "ip", "name": "s", "table": "t", "type": {"typeof": {"concat": [{"payload": {"protocol": "udp", "field": "length"}}, {"payload": {"base": "ih", "offset": 32, "len": 32}}]}}, "handle": 0, "map": "verdict", "flags": "interval"}}} + J {"add": {"element": {"family": "ip", "table": "t", "name": "s", "elem": {"set": [[{"concat": [{"range": [20, 80]}, 20]}, {"accept": null}]]}}}} + J {"add": {"element": {"family": "ip", "table": "t", "name": "s", "elem": {"set": [[{"concat": [{"range": [1, 10]}, 10]}, {"drop": null}]]}}}} +diff --git a/tests/monitor/testcases/set-interval.t b/tests/monitor/testcases/set-interval.t +index 5053c59..84cf98c 100644 +--- a/tests/monitor/testcases/set-interval.t ++++ b/tests/monitor/testcases/set-interval.t +@@ -10,7 +10,7 @@ I add set ip t s { type inet_service; flags interval; elements = { 20, 30-40 }; + O add set ip t s { type inet_service; flags interval; } + O add element ip t s { 20 } + O add element ip t s { 30-40 } +-J {"add": {"set": {"family": "ip", "name": "s", "table": "t", "type": "inet_service", "handle": 0, "flags": ["interval"]}}} ++J {"add": {"set": {"family": "ip", "name": "s", "table": "t", "type": "inet_service", "handle": 0, "flags": "interval"}}} + J {"add": {"element": {"family": "ip", "table": "t", "name": "s", "elem": {"set": [20]}}}} + J {"add": {"element": {"family": "ip", "table": "t", "name": "s", "elem": {"set": [{"range": [30, 40]}]}}}} + +diff --git a/tests/monitor/testcases/set-maps.t b/tests/monitor/testcases/set-maps.t +index acda480..aaf332f 100644 +--- a/tests/monitor/testcases/set-maps.t ++++ b/tests/monitor/testcases/set-maps.t +@@ -3,7 +3,7 @@ I add table ip t + I add map ip t portip { type inet_service: ipv4_addr; flags interval; } + O - + J {"add": {"table": {"family": "ip", "name": "t", "handle": 0}}} +-J {"add": {"map": {"family": "ip", "name": "portip", "table": "t", "type": "inet_service", "handle": 0, "map": "ipv4_addr", "flags": ["interval"]}}} ++J {"add": {"map": {"family": "ip", "name": "portip", "table": "t", "type": "inet_service", "handle": 0, "map": "ipv4_addr", "flags": "interval"}}} + + I add element ip t portip { 80-100: 10.0.0.1 } + O - +diff --git a/tests/monitor/testcases/set-mixed.t b/tests/monitor/testcases/set-mixed.t +index 08c2011..1cf3d38 100644 +--- a/tests/monitor/testcases/set-mixed.t ++++ b/tests/monitor/testcases/set-mixed.t +@@ -4,7 +4,7 @@ I add set ip t portrange { type inet_service; flags interval; } + I add set ip t ports { type inet_service; } + O - + J {"add": {"table": {"family": "ip", "name": "t", "handle": 0}}} +-J {"add": {"set": {"family": "ip", "name": "portrange", "table": "t", "type": "inet_service", "handle": 0, "flags": ["interval"]}}} ++J {"add": {"set": {"family": "ip", "name": "portrange", "table": "t", "type": "inet_service", "handle": 0, "flags": "interval"}}} + J {"add": {"set": {"family": "ip", "name": "ports", "table": "t", "type": "inet_service", "handle": 0}}} + + # make sure concurrent adds work +diff --git a/tests/monitor/testcases/set-multiple.t b/tests/monitor/testcases/set-multiple.t +index bd7a624..84de98e 100644 +--- a/tests/monitor/testcases/set-multiple.t ++++ b/tests/monitor/testcases/set-multiple.t +@@ -4,8 +4,8 @@ I add set ip t portrange { type inet_service; flags interval; } + I add set ip t portrange2 { type inet_service; flags interval; } + O - + J {"add": {"table": {"family": "ip", "name": "t", "handle": 0}}} +-J {"add": {"set": {"family": "ip", "name": "portrange", "table": "t", "type": "inet_service", "handle": 0, "flags": ["interval"]}}} +-J {"add": {"set": {"family": "ip", "name": "portrange2", "table": "t", "type": "inet_service", "handle": 0, "flags": ["interval"]}}} ++J {"add": {"set": {"family": "ip", "name": "portrange", "table": "t", "type": "inet_service", "handle": 0, "flags": "interval"}}} ++J {"add": {"set": {"family": "ip", "name": "portrange2", "table": "t", "type": "inet_service", "handle": 0, "flags": "interval"}}} + + # make sure concurrent adds work + I add element ip t portrange { 1024-65535 } +diff --git a/tests/monitor/testcases/set-simple.t b/tests/monitor/testcases/set-simple.t +index 6853a0e..4bef144 100644 +--- a/tests/monitor/testcases/set-simple.t ++++ b/tests/monitor/testcases/set-simple.t +@@ -3,7 +3,7 @@ I add table ip t + I add set ip t portrange { type inet_service; flags interval; } + O - + J {"add": {"table": {"family": "ip", "name": "t", "handle": 0}}} +-J {"add": {"set": {"family": "ip", "name": "portrange", "table": "t", "type": "inet_service", "handle": 0, "flags": ["interval"]}}} ++J {"add": {"set": {"family": "ip", "name": "portrange", "table": "t", "type": "inet_service", "handle": 0, "flags": "interval"}}} + + # adding some ranges + I add element ip t portrange { 1-10 } diff --git a/0212-tests-shell-combine-dormant-flag-with-netdevice-remo.patch b/0212-tests-shell-combine-dormant-flag-with-netdevice-remo.patch new file mode 100644 index 0000000..a53a27e --- /dev/null +++ b/0212-tests-shell-combine-dormant-flag-with-netdevice-remo.patch @@ -0,0 +1,39 @@ +From 640c7a71905492b32eea65ab227f3d2792e6509b Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:14:04 +0200 +Subject: [PATCH] tests: shell: combine dormant flag with netdevice removal + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit f09171e077f8a9642593e3807e5ca22828befb08 + +commit f09171e077f8a9642593e3807e5ca22828befb08 +Author: Pablo Neira Ayuso +Date: Wed Apr 24 20:43:58 2024 +0200 + + tests: shell: combine dormant flag with netdevice removal + + Exercise table is dormant and netdevice is gone combination. + + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + .../testcases/chains/netdev_chain_dormant_autoremove | 9 +++++++++ + 1 file changed, 9 insertions(+) + create mode 100755 tests/shell/testcases/chains/netdev_chain_dormant_autoremove + +diff --git a/tests/shell/testcases/chains/netdev_chain_dormant_autoremove b/tests/shell/testcases/chains/netdev_chain_dormant_autoremove +new file mode 100755 +index 0000000..0a684e5 +--- /dev/null ++++ b/tests/shell/testcases/chains/netdev_chain_dormant_autoremove +@@ -0,0 +1,9 @@ ++#!/bin/bash ++ ++set -e ++ ++ip link add dummy0 type dummy ++ip link add dummy1 type dummy ++$NFT add table netdev test { flags dormant\; } ++$NFT add chain netdev test ingress { type filter hook ingress devices = { "dummy0", "dummy1" } priority 0\; policy drop\; } ++ip link del dummy0 diff --git a/0213-tests-shell-Adjust-to-ifname-based-hooks.patch b/0213-tests-shell-Adjust-to-ifname-based-hooks.patch new file mode 100644 index 0000000..71e09f7 --- /dev/null +++ b/0213-tests-shell-Adjust-to-ifname-based-hooks.patch @@ -0,0 +1,121 @@ +From 4c3f2e77cbaf6a26584af6c5695721dff5920e90 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:14:04 +0200 +Subject: [PATCH] tests: shell: Adjust to ifname-based hooks + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit f27e5abd81f29d6429e2749a6adf02a25185e206 + +commit f27e5abd81f29d6429e2749a6adf02a25185e206 +Author: Phil Sutter +Date: Wed May 7 00:06:20 2025 +0200 + + tests: shell: Adjust to ifname-based hooks + + Interface specs won't disappear anymore upon device removal. Drop them + manually if kernel has ifname-based hooks. + + Skip transactions/0050rule_1 if kernel has name-based hooks: The test + relies upon the ruleset being rejected for non-existent interfaces, + which obviously won't happen then. + + Signed-off-by: Phil Sutter + Reviewed-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + tests/shell/features/ifname_based_hooks.sh | 12 ++++++++++++ + .../testcases/chains/netdev_chain_dormant_autoremove | 3 +++ + .../testcases/flowtable/0012flowtable_variable_0 | 9 ++++++++- + tests/shell/testcases/listing/0020flowtable_0 | 8 +++++++- + tests/shell/testcases/transactions/0050rule_1 | 4 ++++ + 5 files changed, 34 insertions(+), 2 deletions(-) + create mode 100755 tests/shell/features/ifname_based_hooks.sh + +diff --git a/tests/shell/features/ifname_based_hooks.sh b/tests/shell/features/ifname_based_hooks.sh +new file mode 100755 +index 0000000..cada695 +--- /dev/null ++++ b/tests/shell/features/ifname_based_hooks.sh +@@ -0,0 +1,12 @@ ++#!/bin/bash ++ ++# check if netdev chains survive without a single device ++ ++unshare -n bash -c "ip link add d0 type dummy; \ ++ $NFT \"table netdev t { \ ++ chain c { \ ++ type filter hook ingress priority 0; devices = { d0 }; \ ++ }; \ ++ }\"; \ ++ ip link del d0; \ ++ $NFT list chain netdev t c" +diff --git a/tests/shell/testcases/chains/netdev_chain_dormant_autoremove b/tests/shell/testcases/chains/netdev_chain_dormant_autoremove +index 0a684e5..659ab69 100755 +--- a/tests/shell/testcases/chains/netdev_chain_dormant_autoremove ++++ b/tests/shell/testcases/chains/netdev_chain_dormant_autoremove +@@ -7,3 +7,6 @@ ip link add dummy1 type dummy + $NFT add table netdev test { flags dormant\; } + $NFT add chain netdev test ingress { type filter hook ingress devices = { "dummy0", "dummy1" } priority 0\; policy drop\; } + ip link del dummy0 ++if [ "$NFT_TEST_HAVE_ifname_based_hooks" = y ]; then ++ $NFT 'delete chain netdev test ingress { devices = { "dummy0" }; }' ++fi +diff --git a/tests/shell/testcases/flowtable/0012flowtable_variable_0 b/tests/shell/testcases/flowtable/0012flowtable_variable_0 +index 9c03820..ff35548 100755 +--- a/tests/shell/testcases/flowtable/0012flowtable_variable_0 ++++ b/tests/shell/testcases/flowtable/0012flowtable_variable_0 +@@ -4,11 +4,18 @@ + + set -e + ++ft_deldev() { ++ $NFT "delete flowtable $1 $2 { devices = { $3 }; }" ++} ++ + iface_cleanup() { + ip link del dummy1 &>/dev/null || : ++ if [ "$NFT_TEST_HAVE_ifname_based_hooks" = y ]; then ++ ft_deldev filter1 Main_ft1 dummy1 ++ ft_deldev filter2 Main_ft2 dummy1 ++ fi + } + trap 'iface_cleanup' EXIT +-iface_cleanup + + ip link add name dummy1 type dummy + +diff --git a/tests/shell/testcases/listing/0020flowtable_0 b/tests/shell/testcases/listing/0020flowtable_0 +index 6eb82cf..600b524 100755 +--- a/tests/shell/testcases/listing/0020flowtable_0 ++++ b/tests/shell/testcases/listing/0020flowtable_0 +@@ -46,7 +46,13 @@ EXPECTED3="table ip filter { + iface_cleanup() { + ip link del d0 &>/dev/null || : + } +-trap 'iface_cleanup' EXIT ++ft_cleanup() { ++ if [ "$NFT_TEST_HAVE_ifname_based_hooks" = y ]; then ++ $NFT 'delete flowtable ip filter f2 { devices = { d0 }; }' ++ $NFT 'delete flowtable inet filter f2 { devices = { d0 }; }' ++ fi ++} ++trap 'iface_cleanup; ft_cleanup' EXIT + iface_cleanup + + ip link add d0 type dummy +diff --git a/tests/shell/testcases/transactions/0050rule_1 b/tests/shell/testcases/transactions/0050rule_1 +index 89e5f42..cbf9689 100755 +--- a/tests/shell/testcases/transactions/0050rule_1 ++++ b/tests/shell/testcases/transactions/0050rule_1 +@@ -2,6 +2,10 @@ + + set -e + ++# The ruleset is supposed to fail due to non-existing interfaces, ++# hence depends on non-name-based interface hooks. ++[ "$NFT_TEST_HAVE_ifname_based_hooks" = y ] && exit 77 ++ + RULESET="table inet filter { + flowtable ftable { + hook ingress priority 0; devices = { eno1, eno0, x }; diff --git a/0214-tests-py-Properly-fix-JSON-equivalents-for-netdev-re.patch b/0214-tests-py-Properly-fix-JSON-equivalents-for-netdev-re.patch new file mode 100644 index 0000000..6039d25 --- /dev/null +++ b/0214-tests-py-Properly-fix-JSON-equivalents-for-netdev-re.patch @@ -0,0 +1,232 @@ +From 63408cb71377404ecc2584c5b2b83eccd405763d Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:14:04 +0200 +Subject: [PATCH] tests: py: Properly fix JSON equivalents for netdev/reject.t + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit b7a11c691d459d06a801a28ae9a52bc564584a1a + +commit b7a11c691d459d06a801a28ae9a52bc564584a1a +Author: Phil Sutter +Date: Thu Jun 12 12:59:29 2025 +0200 + + tests: py: Properly fix JSON equivalents for netdev/reject.t + + Revert commit d1a7b9e19fe65 ("tests: py: update netdev reject test + file"), the stored JSON equivalents were correct in that they matched + the standard syntax input. + + In fact, we missed a .json.output file recording the expected deviation + in JSON output. + + Fixes: d1a7b9e19fe65 ("tests: py: update netdev reject test file") + Fixes: 7ca3368cd7575 ("reject: Unify inet, netdev and bridge delinearization") + Signed-off-by: Phil Sutter + Reviewed-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + tests/py/netdev/reject.t.json | 66 +++++++++++++++-------- + tests/py/netdev/reject.t.json.output | 81 ++++++++++++++++++++++++++++ + 2 files changed, 126 insertions(+), 21 deletions(-) + create mode 100644 tests/py/netdev/reject.t.json.output + +diff --git a/tests/py/netdev/reject.t.json b/tests/py/netdev/reject.t.json +index 9968aaf..b80db03 100644 +--- a/tests/py/netdev/reject.t.json ++++ b/tests/py/netdev/reject.t.json +@@ -130,17 +130,6 @@ + + # mark 12345 reject with tcp reset + [ +- { +- "match": { +- "left": { +- "meta": { +- "key": "l4proto" +- } +- }, +- "op": "==", +- "right": 6 +- } +- }, + { + "match": { + "left": { +@@ -162,30 +151,43 @@ + # reject + [ + { +- "reject": { +- "expr": "port-unreachable", +- "type": "icmpx" +- } ++ "reject": null + } + ] + + # meta protocol ip reject + [ + { +- "reject": { +- "expr": "port-unreachable", +- "type": "icmp" ++ "match": { ++ "left": { ++ "meta": { ++ "key": "protocol" ++ } ++ }, ++ "op": "==", ++ "right": "ip" + } ++ }, ++ { ++ "reject": null + } + ] + + # meta protocol ip6 reject + [ + { +- "reject": { +- "expr": "port-unreachable", +- "type": "icmpv6" ++ "match": { ++ "left": { ++ "meta": { ++ "key": "protocol" ++ } ++ }, ++ "op": "==", ++ "right": "ip6" + } ++ }, ++ { ++ "reject": null + } + ] + +@@ -231,6 +233,17 @@ + + # meta protocol ip reject with icmp host-unreachable + [ ++ { ++ "match": { ++ "left": { ++ "meta": { ++ "key": "protocol" ++ } ++ }, ++ "op": "==", ++ "right": "ip" ++ } ++ }, + { + "reject": { + "expr": "host-unreachable", +@@ -241,6 +254,17 @@ + + # meta protocol ip6 reject with icmpv6 no-route + [ ++ { ++ "match": { ++ "left": { ++ "meta": { ++ "key": "protocol" ++ } ++ }, ++ "op": "==", ++ "right": "ip6" ++ } ++ }, + { + "reject": { + "expr": "no-route", +diff --git a/tests/py/netdev/reject.t.json.output b/tests/py/netdev/reject.t.json.output +new file mode 100644 +index 0000000..cbd7310 +--- /dev/null ++++ b/tests/py/netdev/reject.t.json.output +@@ -0,0 +1,81 @@ ++# mark 12345 reject with tcp reset ++[ ++ { ++ "match": { ++ "left": { ++ "meta": { ++ "key": "l4proto" ++ } ++ }, ++ "op": "==", ++ "right": 6 ++ } ++ }, ++ { ++ "match": { ++ "left": { ++ "meta": { ++ "key": "mark" ++ } ++ }, ++ "op": "==", ++ "right": 12345 ++ } ++ }, ++ { ++ "reject": { ++ "type": "tcp reset" ++ } ++ } ++] ++ ++# reject ++[ ++ { ++ "reject": { ++ "expr": "port-unreachable", ++ "type": "icmpx" ++ } ++ } ++] ++ ++# meta protocol ip reject ++[ ++ { ++ "reject": { ++ "expr": "port-unreachable", ++ "type": "icmp" ++ } ++ } ++] ++ ++# meta protocol ip6 reject ++[ ++ { ++ "reject": { ++ "expr": "port-unreachable", ++ "type": "icmpv6" ++ } ++ } ++] ++ ++# meta protocol ip reject with icmp host-unreachable ++[ ++ { ++ "reject": { ++ "expr": "host-unreachable", ++ "type": "icmp" ++ } ++ } ++] ++ ++# meta protocol ip6 reject with icmpv6 no-route ++[ ++ { ++ "reject": { ++ "expr": "no-route", ++ "type": "icmpv6" ++ } ++ } ++] ++ diff --git a/0215-netlink-Avoid-crash-upon-missing-NFTNL_OBJ_CT_TIMEOU.patch b/0215-netlink-Avoid-crash-upon-missing-NFTNL_OBJ_CT_TIMEOU.patch new file mode 100644 index 0000000..26e14bb --- /dev/null +++ b/0215-netlink-Avoid-crash-upon-missing-NFTNL_OBJ_CT_TIMEOU.patch @@ -0,0 +1,44 @@ +From 610961b78d6a88b1587bd6933009215940d05481 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:14:04 +0200 +Subject: [PATCH] netlink: Avoid crash upon missing NFTNL_OBJ_CT_TIMEOUT_ARRAY + attribute + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 2a38f458f12bc032dac1b3ba63f95ca5a3c03fbd + +commit 2a38f458f12bc032dac1b3ba63f95ca5a3c03fbd +Author: Phil Sutter +Date: Thu Jun 12 20:17:22 2025 +0200 + + netlink: Avoid crash upon missing NFTNL_OBJ_CT_TIMEOUT_ARRAY attribute + + If missing, the memcpy call ends up reading from address zero. + + Fixes: c7c94802679cd ("src: add ct timeout support") + Signed-off-by: Phil Sutter + Reviewed-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/netlink.c | 7 ++++--- + 1 file changed, 4 insertions(+), 3 deletions(-) + +diff --git a/src/netlink.c b/src/netlink.c +index df7ce21..8fe6881 100644 +--- a/src/netlink.c ++++ b/src/netlink.c +@@ -1734,9 +1734,10 @@ struct obj *netlink_delinearize_obj(struct netlink_ctx *ctx, + init_list_head(&obj->ct_timeout.timeout_list); + obj->ct_timeout.l3proto = nftnl_obj_get_u16(nlo, NFTNL_OBJ_CT_TIMEOUT_L3PROTO); + obj->ct_timeout.l4proto = nftnl_obj_get_u8(nlo, NFTNL_OBJ_CT_TIMEOUT_L4PROTO); +- memcpy(obj->ct_timeout.timeout, +- nftnl_obj_get(nlo, NFTNL_OBJ_CT_TIMEOUT_ARRAY), +- NFTNL_CTTIMEOUT_ARRAY_MAX * sizeof(uint32_t)); ++ if (nftnl_obj_is_set(nlo, NFTNL_OBJ_CT_TIMEOUT_ARRAY)) ++ memcpy(obj->ct_timeout.timeout, ++ nftnl_obj_get(nlo, NFTNL_OBJ_CT_TIMEOUT_ARRAY), ++ NFTNL_CTTIMEOUT_ARRAY_MAX * sizeof(uint32_t)); + break; + case NFT_OBJECT_LIMIT: + obj->limit.rate = diff --git a/0216-src-BASECHAIN-flag-no-longer-implies-presence-of-pri.patch b/0216-src-BASECHAIN-flag-no-longer-implies-presence-of-pri.patch new file mode 100644 index 0000000..d646ec0 --- /dev/null +++ b/0216-src-BASECHAIN-flag-no-longer-implies-presence-of-pri.patch @@ -0,0 +1,77 @@ +From 1a6c930476d44c9e6ee77b71a9acbb03881eba8f Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:14:04 +0200 +Subject: [PATCH] src: BASECHAIN flag no longer implies presence of priority + expression + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 44ea1936463728475768861073ca4ba34a5c2f75 + +commit 44ea1936463728475768861073ca4ba34a5c2f75 +Author: Florian Westphal +Date: Thu Jun 12 20:17:15 2025 +0200 + + src: BASECHAIN flag no longer implies presence of priority expression + + The included bogon will crash nft because print side assumes that BASECHAIN + flag presence also means that priority expression is available. + + Make the print side conditional. + + Fixes: a66b5ad9540d ("src: allow for updating devices on existing netdev chain") + Suggested-by: Pablo Neira Ayuso + Signed-off-by: Florian Westphal + +Signed-off-by: Phil Sutter +--- + src/rule.c | 16 ++++++++++------ + .../bogons/nft-f/null_ingress_type_crash | 6 ++++++ + 2 files changed, 16 insertions(+), 6 deletions(-) + create mode 100644 tests/shell/testcases/bogons/nft-f/null_ingress_type_crash + +diff --git a/src/rule.c b/src/rule.c +index 6f247a1..9a54a99 100644 +--- a/src/rule.c ++++ b/src/rule.c +@@ -1045,8 +1045,10 @@ static void chain_print_declaration(const struct chain *chain, + nft_print(octx, "\n\t\tcomment \"%s\"", chain->comment); + nft_print(octx, "\n"); + if (chain->flags & CHAIN_F_BASECHAIN) { +- nft_print(octx, "\t\ttype %s hook %s", chain->type.str, +- hooknum2str(chain->handle.family, chain->hook.num)); ++ if (chain->type.str) ++ nft_print(octx, "\t\ttype %s hook %s", chain->type.str, ++ hooknum2str(chain->handle.family, chain->hook.num)); ++ + if (chain->dev_array_len == 1) { + nft_print(octx, " device \"%s\"", chain->dev_array[0]); + } else if (chain->dev_array_len > 1) { +@@ -1058,10 +1060,12 @@ static void chain_print_declaration(const struct chain *chain, + } + nft_print(octx, " }"); + } +- nft_print(octx, " priority %s;", +- prio2str(octx, priobuf, sizeof(priobuf), +- chain->handle.family, chain->hook.num, +- chain->priority.expr)); ++ ++ if (chain->priority.expr) ++ nft_print(octx, " priority %s;", ++ prio2str(octx, priobuf, sizeof(priobuf), ++ chain->handle.family, chain->hook.num, ++ chain->priority.expr)); + if (chain->policy) { + mpz_export_data(&policy, chain->policy->value, + BYTEORDER_HOST_ENDIAN, sizeof(int)); +diff --git a/tests/shell/testcases/bogons/nft-f/null_ingress_type_crash b/tests/shell/testcases/bogons/nft-f/null_ingress_type_crash +new file mode 100644 +index 0000000..2ed88af +--- /dev/null ++++ b/tests/shell/testcases/bogons/nft-f/null_ingress_type_crash +@@ -0,0 +1,6 @@ ++table netdev filter1 { ++ chain c { ++ devices = { lo } ++ } ++} ++list ruleset diff --git a/0217-tests-py-prepare-for-set-debug-change.patch b/0217-tests-py-prepare-for-set-debug-change.patch new file mode 100644 index 0000000..723b326 --- /dev/null +++ b/0217-tests-py-prepare-for-set-debug-change.patch @@ -0,0 +1,73 @@ +From 2f21c29c1e3490f9a0074713bcd1db74b7e78b89 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:14:04 +0200 +Subject: [PATCH] tests/py: prepare for set debug change + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 7cec20e45a7549d82b7eac8eac6c8a31458fe291 + +commit 7cec20e45a7549d82b7eac8eac6c8a31458fe291 +Author: Florian Westphal +Date: Tue Apr 8 16:21:29 2025 +0200 + + tests/py: prepare for set debug change + + Next patch will make initial set dump from kernel emit set debug + information, so the obtained netlink debug file won't match what is + recorded in tests/py. + + Furthermore, as the python add rules for each of the family the test is + for, subsequent dump will include debug information of the other/previous + families. + + Change the script to skip all unrelated information to only compare the + relevant set element information and the generated expressions. + + This change still finds changes in [ expr ... ] and set elem debug output. + + Signed-off-by: Florian Westphal + +Signed-off-by: Phil Sutter +--- + tests/py/nft-test.py | 15 ++++++++++----- + 1 file changed, 10 insertions(+), 5 deletions(-) + +diff --git a/tests/py/nft-test.py b/tests/py/nft-test.py +index 90c7612..06f1701 100755 +--- a/tests/py/nft-test.py ++++ b/tests/py/nft-test.py +@@ -717,17 +717,20 @@ def payload_check(payload_buffer, file, cmd): + return False + + for lineno, want_line in enumerate(payload_buffer): ++ # skip irreleant parts, such as "ip test-ipv4 output" ++ if want_line.find("[") < 0 or want_line.find("]") < 0: ++ continue ++ + line = file.readline() ++ while line.find("[") < 0 or line.find("]") < 0: ++ line = file.readline() ++ if line == "": ++ break + + if want_line == line: + i += 1 + continue + +- if want_line.find('[') < 0 and line.find('[') < 0: +- continue +- if want_line.find(']') < 0 and line.find(']') < 0: +- continue +- + if payload_check_set_elems(want_line, line): + continue + +@@ -875,6 +878,8 @@ def rule_add(rule, filename, lineno, force_all_family_option, filename_path): + gotf.write("# %s\n" % rule[0]) + while True: + line = payload_log.readline() ++ if line.startswith("family "): ++ continue + if line == "": + break + gotf.write(line) diff --git a/0218-debug-include-kernel-set-information-on-cache-fill.patch b/0218-debug-include-kernel-set-information-on-cache-fill.patch new file mode 100644 index 0000000..e03b4e4 --- /dev/null +++ b/0218-debug-include-kernel-set-information-on-cache-fill.patch @@ -0,0 +1,102 @@ +From 7469c33958c928ef36863b39f0ab45d36152fc56 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:14:04 +0200 +Subject: [PATCH] debug: include kernel set information on cache fill + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 6063a4644746d12bebb39f3ca93e76de6392f5ec + +commit 6063a4644746d12bebb39f3ca93e76de6392f5ec +Author: Florian Westphal +Date: Tue Apr 8 16:21:30 2025 +0200 + + debug: include kernel set information on cache fill + + Honor --debug=netlink flag also when doing initial set dump + from the kernel. + + With recent libnftnl update this will include the chosen + set backend name that is used by the kernel. + + Because set names are scoped by table and protocol family, + also include the family protocol number. + + Dumping this information breaks tests/py as the recorded + debug output no longer matches, this is fixed in previous + change. + + Signed-off-by: Florian Westphal + +Signed-off-by: Phil Sutter +--- + src/mnl.c | 15 +++++++++++++-- + src/netlink.c | 3 +++ + 2 files changed, 16 insertions(+), 2 deletions(-) + +diff --git a/src/mnl.c b/src/mnl.c +index 0f54d00..d9f58de 100644 +--- a/src/mnl.c ++++ b/src/mnl.c +@@ -1376,9 +1376,15 @@ int mnl_nft_set_del(struct netlink_ctx *ctx, struct cmd *cmd) + return 0; + } + ++struct set_cb_args { ++ struct netlink_ctx *ctx; ++ struct nftnl_set_list *list; ++}; ++ + static int set_cb(const struct nlmsghdr *nlh, void *data) + { +- struct nftnl_set_list *nls_list = data; ++ struct set_cb_args *args = data; ++ struct nftnl_set_list *nls_list = args->list; + struct nftnl_set *s; + + if (check_genid(nlh) < 0) +@@ -1391,6 +1397,8 @@ static int set_cb(const struct nlmsghdr *nlh, void *data) + if (nftnl_set_nlmsg_parse(nlh, s) < 0) + goto err_free; + ++ netlink_dump_set(s, args->ctx); ++ + nftnl_set_list_add_tail(s, nls_list); + return MNL_CB_OK; + +@@ -1409,6 +1417,7 @@ mnl_nft_set_dump(struct netlink_ctx *ctx, int family, + struct nlmsghdr *nlh; + struct nftnl_set *s; + int ret; ++ struct set_cb_args args; + + s = nftnl_set_alloc(); + if (s == NULL) +@@ -1430,7 +1439,9 @@ mnl_nft_set_dump(struct netlink_ctx *ctx, int family, + if (nls_list == NULL) + memory_allocation_error(); + +- ret = nft_mnl_talk(ctx, nlh, nlh->nlmsg_len, set_cb, nls_list); ++ args.list = nls_list; ++ args.ctx = ctx; ++ ret = nft_mnl_talk(ctx, nlh, nlh->nlmsg_len, set_cb, &args); + if (ret < 0 && errno != ENOENT) + goto err; + +diff --git a/src/netlink.c b/src/netlink.c +index 8fe6881..c1d3e45 100644 +--- a/src/netlink.c ++++ b/src/netlink.c +@@ -843,10 +843,13 @@ static const struct datatype *dtype_map_from_kernel(enum nft_data_types type) + void netlink_dump_set(const struct nftnl_set *nls, struct netlink_ctx *ctx) + { + FILE *fp = ctx->nft->output.output_fp; ++ uint32_t family; + + if (!(ctx->nft->debug_mask & NFT_DEBUG_NETLINK) || !fp) + return; + ++ family = nftnl_set_get_u32(nls, NFTNL_SET_FAMILY); ++ fprintf(fp, "family %d ", family); + nftnl_set_fprintf(fp, nls, 0, 0); + fprintf(fp, "\n"); + } diff --git a/0219-parser_bison-allow-delete-command-with-map-via-handl.patch b/0219-parser_bison-allow-delete-command-with-map-via-handl.patch new file mode 100644 index 0000000..f8130dd --- /dev/null +++ b/0219-parser_bison-allow-delete-command-with-map-via-handl.patch @@ -0,0 +1,66 @@ +From fe34381b48689be42d8fd02543d12b0a99d8590c Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:14:04 +0200 +Subject: [PATCH] parser_bison: allow delete command with map via handle + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 640312b1529c548790117635c91886a6c83e83f2 + +commit 640312b1529c548790117635c91886a6c83e83f2 +Author: Pablo Neira Ayuso +Date: Sun Jun 15 11:36:28 2025 +0200 + + parser_bison: allow delete command with map via handle + + For consistency with sets, allow delete via handle for maps too. + + Fixes: f4a34d25f6d5 ("src: list set handle and delete set via set handle") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/parser_bison.y | 2 +- + tests/shell/testcases/cache/0008_delete_by_handle_0 | 4 ++++ + tests/shell/testcases/cache/0009_delete_by_handle_incorrect_0 | 1 + + 3 files changed, 6 insertions(+), 1 deletion(-) + +diff --git a/src/parser_bison.y b/src/parser_bison.y +index 47482fa..67a35e1 100644 +--- a/src/parser_bison.y ++++ b/src/parser_bison.y +@@ -1448,7 +1448,7 @@ delete_cmd : TABLE table_or_id_spec + { + $$ = cmd_alloc(CMD_DELETE, CMD_OBJ_SET, &$2, &@$, NULL); + } +- | MAP set_spec ++ | MAP set_or_id_spec + { + $$ = cmd_alloc(CMD_DELETE, CMD_OBJ_SET, &$2, &@$, NULL); + } +diff --git a/tests/shell/testcases/cache/0008_delete_by_handle_0 b/tests/shell/testcases/cache/0008_delete_by_handle_0 +index 0db4c69..9eb75e6 100755 +--- a/tests/shell/testcases/cache/0008_delete_by_handle_0 ++++ b/tests/shell/testcases/cache/0008_delete_by_handle_0 +@@ -16,6 +16,10 @@ $NFT add set t s { type ipv4_addr\; } + HANDLE=`$NFT -a list ruleset | grep "set.*handle" | cut -d' ' -f6` + $NFT delete set t handle $HANDLE + ++$NFT add map t m { type ipv4_addr : ipv4_addr\; } ++HANDLE=`$NFT -a list ruleset | grep "map.*handle" | cut -d' ' -f6` ++$NFT delete map t handle $HANDLE ++ + $NFT add flowtable t f { hook ingress priority 0\; devices = { lo } \; } + HANDLE=`$NFT -a list ruleset | grep "flowtable.*handle" | cut -d' ' -f6` + $NFT delete flowtable t handle $HANDLE +diff --git a/tests/shell/testcases/cache/0009_delete_by_handle_incorrect_0 b/tests/shell/testcases/cache/0009_delete_by_handle_incorrect_0 +index f0bb02a..dd390e7 100755 +--- a/tests/shell/testcases/cache/0009_delete_by_handle_incorrect_0 ++++ b/tests/shell/testcases/cache/0009_delete_by_handle_incorrect_0 +@@ -3,6 +3,7 @@ + $NFT delete table handle 4000 && exit 1 + $NFT delete chain t handle 4000 && exit 1 + $NFT delete set t handle 4000 && exit 1 ++$NFT delete map t handle 4000 && exit 1 + $NFT delete flowtable t handle 4000 && exit 1 + $NFT delete counter t handle 4000 && exit 1 + exit 0 diff --git a/0220-rule-skip-fuzzy-lookup-if-object-name-is-not-availab.patch b/0220-rule-skip-fuzzy-lookup-if-object-name-is-not-availab.patch new file mode 100644 index 0000000..492ccf0 --- /dev/null +++ b/0220-rule-skip-fuzzy-lookup-if-object-name-is-not-availab.patch @@ -0,0 +1,73 @@ +From 05104a35a526faed1e5504ba68b24095e6a6aab0 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:14:04 +0200 +Subject: [PATCH] rule: skip fuzzy lookup if object name is not available + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit de8396358f869d6d7640eae6d6287c2f7fb0d3dc + +commit de8396358f869d6d7640eae6d6287c2f7fb0d3dc +Author: Pablo Neira Ayuso +Date: Sun Jun 15 11:33:42 2025 +0200 + + rule: skip fuzzy lookup if object name is not available + + Skip fuzzy lookup for suggestions when handles are used. + + Note that 4cf97abfee61 ("rule: Avoid segfault with anonymous chains") + already skips it for chain. + + Fixes: 285bb67a11ad ("src: introduce simple hints on incorrect set") + Fixes: 9f7817a4e022 ("src: introduce simple hints on incorrect chain") + Fixes: d7476ddd5f7d ("src: introduce simple hints on incorrect table") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/rule.c | 12 ++++++++++++ + 1 file changed, 12 insertions(+) + +diff --git a/src/rule.c b/src/rule.c +index 9a54a99..bfcf4a2 100644 +--- a/src/rule.c ++++ b/src/rule.c +@@ -211,6 +211,9 @@ struct set *set_lookup_fuzzy(const char *set_name, + struct table *table; + struct set *set; + ++ if (!set_name) ++ return NULL; ++ + string_misspell_init(&st); + + list_for_each_entry(table, &cache->table_cache.list, cache.list) { +@@ -1216,6 +1219,9 @@ struct table *table_lookup_fuzzy(const struct handle *h, + struct string_misspell_state st; + struct table *table; + ++ if (!h->table.name) ++ return NULL; ++ + string_misspell_init(&st); + + list_for_each_entry(table, &cache->table_cache.list, cache.list) { +@@ -1695,6 +1701,9 @@ struct obj *obj_lookup_fuzzy(const char *obj_name, + struct table *table; + struct obj *obj; + ++ if (!obj_name) ++ return NULL; ++ + string_misspell_init(&st); + + list_for_each_entry(table, &cache->table_cache.list, cache.list) { +@@ -2172,6 +2181,9 @@ struct flowtable *flowtable_lookup_fuzzy(const char *ft_name, + struct table *table; + struct flowtable *ft; + ++ if (!ft_name) ++ return NULL; ++ + string_misspell_init(&st); + + list_for_each_entry(table, &cache->table_cache.list, cache.list) { diff --git a/0221-parser_bison-only-reset-by-name-is-supported-by-now.patch b/0221-parser_bison-only-reset-by-name-is-supported-by-now.patch new file mode 100644 index 0000000..5677fcc --- /dev/null +++ b/0221-parser_bison-only-reset-by-name-is-supported-by-now.patch @@ -0,0 +1,55 @@ +From 3d06fd69116855f116796d628a91c2c106d57c70 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:14:04 +0200 +Subject: [PATCH] parser_bison: only reset by name is supported by now + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit e8c7ba0aac7ce321e61008fe9b4b8f11c3ba7e1d + +commit e8c7ba0aac7ce321e61008fe9b4b8f11c3ba7e1d +Author: Pablo Neira Ayuso +Date: Sun Jun 15 11:34:11 2025 +0200 + + parser_bison: only reset by name is supported by now + + NFT_MSG_GETSET does not support for handle lookup yet, restrict this to + reset by name by now. + + Add a bogon test reported by Florian Westphal. + + Fixes: 83e0f4402fb7 ("Implement 'reset {set,map,element}' commands") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/parser_bison.y | 4 ++-- + tests/shell/testcases/bogons/nft-f/null_set_name_crash | 2 ++ + 2 files changed, 4 insertions(+), 2 deletions(-) + create mode 100644 tests/shell/testcases/bogons/nft-f/null_set_name_crash + +diff --git a/src/parser_bison.y b/src/parser_bison.y +index 67a35e1..cdc9809 100644 +--- a/src/parser_bison.y ++++ b/src/parser_bison.y +@@ -1755,11 +1755,11 @@ reset_cmd : COUNTERS list_cmd_spec_any + { + $$ = cmd_alloc(CMD_RESET, CMD_OBJ_ELEMENTS, &$2, &@$, $3); + } +- | SET set_or_id_spec ++ | SET set_spec + { + $$ = cmd_alloc(CMD_RESET, CMD_OBJ_SET, &$2, &@$, NULL); + } +- | MAP set_or_id_spec ++ | MAP set_spec + { + $$ = cmd_alloc(CMD_RESET, CMD_OBJ_MAP, &$2, &@$, NULL); + } +diff --git a/tests/shell/testcases/bogons/nft-f/null_set_name_crash b/tests/shell/testcases/bogons/nft-f/null_set_name_crash +new file mode 100644 +index 0000000..e5d85b2 +--- /dev/null ++++ b/tests/shell/testcases/bogons/nft-f/null_set_name_crash +@@ -0,0 +1,2 @@ ++table y { } ++reset set y handle 6 diff --git a/0222-cache-assert-name-is-non-nul-when-looking-up.patch b/0222-cache-assert-name-is-non-nul-when-looking-up.patch new file mode 100644 index 0000000..df05c45 --- /dev/null +++ b/0222-cache-assert-name-is-non-nul-when-looking-up.patch @@ -0,0 +1,75 @@ +From 0f797a36d51e6f1c0b5b058824a7f8097aed04ef Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:14:04 +0200 +Subject: [PATCH] cache: assert name is non-nul when looking up + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit f15bc7d368b7c1d897fd830f91e7db6929175b27 + +commit f15bc7d368b7c1d897fd830f91e7db6929175b27 +Author: Pablo Neira Ayuso +Date: Sun Jun 15 11:33:49 2025 +0200 + + cache: assert name is non-nul when looking up + + {table,chain,set,obj,flowtable}_cache_find() should not be called when + handles are used + + Fixes: 5ec5c706d993 ("cache: add hashtable cache for table") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/cache.c | 11 +++++++++-- + 1 file changed, 9 insertions(+), 2 deletions(-) + +diff --git a/src/cache.c b/src/cache.c +index 3c624df..b82d0fd 100644 +--- a/src/cache.c ++++ b/src/cache.c +@@ -563,8 +563,7 @@ struct table *table_cache_find(const struct cache *cache, + struct table *table; + uint32_t hash; + +- if (!name) +- return NULL; ++ assert(name); + + hash = djb_hash(name) % NFT_CACHE_HSIZE; + list_for_each_entry(table, &cache->ht[hash], cache.hlist) { +@@ -684,6 +683,8 @@ struct chain *chain_cache_find(const struct table *table, const char *name) + struct chain *chain; + uint32_t hash; + ++ assert(name); ++ + hash = djb_hash(name) % NFT_CACHE_HSIZE; + list_for_each_entry(chain, &table->chain_cache.ht[hash], cache.hlist) { + if (!strcmp(chain->handle.chain.name, name)) +@@ -852,6 +853,8 @@ struct set *set_cache_find(const struct table *table, const char *name) + struct set *set; + uint32_t hash; + ++ assert(name); ++ + hash = djb_hash(name) % NFT_CACHE_HSIZE; + list_for_each_entry(set, &table->set_cache.ht[hash], cache.hlist) { + if (!strcmp(set->handle.set.name, name)) +@@ -967,6 +970,8 @@ struct obj *obj_cache_find(const struct table *table, const char *name, + struct obj *obj; + uint32_t hash; + ++ assert(name); ++ + hash = djb_hash(name) % NFT_CACHE_HSIZE; + list_for_each_entry(obj, &table->obj_cache.ht[hash], cache.hlist) { + if (!strcmp(obj->handle.obj.name, name) && +@@ -1071,6 +1076,8 @@ struct flowtable *ft_cache_find(const struct table *table, const char *name) + struct flowtable *ft; + uint32_t hash; + ++ assert(name); ++ + hash = djb_hash(name) % NFT_CACHE_HSIZE; + list_for_each_entry(ft, &table->ft_cache.ht[hash], cache.hlist) { + if (!strcmp(ft->handle.flowtable.name, name)) diff --git a/0223-tests-py-clean-up-set-backend-support-fallout.patch b/0223-tests-py-clean-up-set-backend-support-fallout.patch new file mode 100644 index 0000000..ffc9098 --- /dev/null +++ b/0223-tests-py-clean-up-set-backend-support-fallout.patch @@ -0,0 +1,48 @@ +From b781894684ace123ce3aa246dd15d546aac2d239 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:14:04 +0200 +Subject: [PATCH] tests/py: clean up set backend support fallout + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit d477eada4f271f5f8774b5f467d937b1439cb46b + +commit d477eada4f271f5f8774b5f467d937b1439cb46b +Author: Florian Westphal +Date: Tue Jun 24 21:39:46 2025 +0200 + + tests/py: clean up set backend support fallout + + Pablo reports failing py tests woth recent kernel and userland: + any/objects.t: OK + WARNING: line 3: 'add rule ip6 test-ip6 input .. + mismatches 'family 2 __set0 test-ip4 3 backend nft_set_bitmap_type [nf_tables] count 7' + + When nf_tables is built as a module, the set backend name coming + from kernel contains the module name ([nf_tables]), this makes the + test script treat it as part of the pseudo instructions. + + Skip this line explicitly to avoid these warnings. + + Fixes: 7cec20e45a75 ("tests/py: prepare for set debug change") + Reported-by: Pablo Neira Ayuso + Tested-by: Pablo Neira Ayuso + Signed-off-by: Florian Westphal + +Signed-off-by: Phil Sutter +--- + tests/py/nft-test.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/tests/py/nft-test.py b/tests/py/nft-test.py +index 06f1701..376e62a 100755 +--- a/tests/py/nft-test.py ++++ b/tests/py/nft-test.py +@@ -722,7 +722,7 @@ def payload_check(payload_buffer, file, cmd): + continue + + line = file.readline() +- while line.find("[") < 0 or line.find("]") < 0: ++ while line.find("[") < 0 or line.find("]") < 0 or (line.startswith("family ") and line.find(" [nf_tables]") > 0): + line = file.readline() + if line == "": + break diff --git a/0224-json-reject-too-long-interface-names.patch b/0224-json-reject-too-long-interface-names.patch new file mode 100644 index 0000000..fcf1d3e --- /dev/null +++ b/0224-json-reject-too-long-interface-names.patch @@ -0,0 +1,172 @@ +From 6f3b94df5380ec1646c30c6471cfc5b284ab543c Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:14:04 +0200 +Subject: [PATCH] json: reject too long interface names + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit bed99830c4c63eae205c28a7ff914737bedb199d + +commit bed99830c4c63eae205c28a7ff914737bedb199d +Author: Florian Westphal +Date: Tue Jun 24 23:46:59 2025 +0200 + + json: reject too long interface names + + Blamed commit added a length check on ifnames to the bison parser. + Unfortunately that wasn't enough, json parser has the same issue. + + Bogon results in: + BUG: Interface length 44 exceeds limit + nft: src/mnl.c:742: nft_dev_add: Assertion `0' failed. + + After patch, included bogon results in: + Error: Invalid device at index 0. name d2345678999999999999999999999999999999012345 too long + + I intentionally did not extend evaluate.c to catch this, past sentiment + was that frontends should not send garbage. + + I'll send a followup patch to also catch this from eval stage in case there + are further reports for frontends passing in such long names. + + Fixes: fa52bc225806 ("parser: reject zero-length interface names") + Signed-off-by: Florian Westphal + Reviewed-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/parser_json.c | 17 +++- + .../nft-j-f/dev_name_parser_overflow_crash | 90 +++++++++++++++++++ + 2 files changed, 105 insertions(+), 2 deletions(-) + create mode 100644 tests/shell/testcases/bogons/nft-j-f/dev_name_parser_overflow_crash + +diff --git a/src/parser_json.c b/src/parser_json.c +index 05dab1a..88bf20a 100644 +--- a/src/parser_json.c ++++ b/src/parser_json.c +@@ -2914,7 +2914,13 @@ static struct expr *json_parse_devs(struct json_ctx *ctx, json_t *root) + size_t index; + + if (!json_unpack(root, "s", &dev)) { +- tmp = constant_expr_alloc(int_loc, &string_type, ++ if (strlen(dev) >= IFNAMSIZ) { ++ json_error(ctx, "Device name %s too long", dev); ++ expr_free(expr); ++ return NULL; ++ } ++ ++ tmp = constant_expr_alloc(int_loc, &ifname_type, + BYTEORDER_HOST_ENDIAN, + strlen(dev) * BITS_PER_BYTE, dev); + compound_expr_add(expr, tmp); +@@ -2932,7 +2938,14 @@ static struct expr *json_parse_devs(struct json_ctx *ctx, json_t *root) + expr_free(expr); + return NULL; + } +- tmp = constant_expr_alloc(int_loc, &string_type, ++ ++ if (strlen(dev) >= IFNAMSIZ) { ++ json_error(ctx, "Device name %s too long at index %zu", dev, index); ++ expr_free(expr); ++ return NULL; ++ } ++ ++ tmp = constant_expr_alloc(int_loc, &ifname_type, + BYTEORDER_HOST_ENDIAN, + strlen(dev) * BITS_PER_BYTE, dev); + compound_expr_add(expr, tmp); +diff --git a/tests/shell/testcases/bogons/nft-j-f/dev_name_parser_overflow_crash b/tests/shell/testcases/bogons/nft-j-f/dev_name_parser_overflow_crash +new file mode 100644 +index 0000000..8303c5c +--- /dev/null ++++ b/tests/shell/testcases/bogons/nft-j-f/dev_name_parser_overflow_crash +@@ -0,0 +1,90 @@ ++{ ++ "nftables": [ ++ { ++ "metainfo": { ++ "version": "VERSION", ++ "release_name": "RELEASE_NAME", ++ "json_schema_version": 1 ++ } ++ }, ++ { ++ "table": { ++ "family": "netdev", ++ "name": "filter1", ++ "handle": 0 ++ } ++ }, ++ { ++ "chain": { ++ "family": "netdev", ++ "table": "filter1", ++ "name": "Main_Ingress1", ++ "handle": 0, ++ "dev": "lo", ++ "type": "filter", ++ "hook": "ingress", ++ "prio": -500, ++ "policy": "accept" ++ } ++ }, ++ { ++ "table": { ++ "family": "netdev", ++ "name": "filter2", ++ "handle": 0 ++ } ++ }, ++ { ++ "chain": { ++ "family": "netdev", ++ "table": "filter2", ++ "name": "Main_Ingress2", ++ "handle": 0, ++ "dev": [ ++ "d2345678999999999999999999999999999999012345", ++ "lo" ++ ], ++ "type": "filter", ++ "hook": "ingress", ++ "prio": -500, ++ "policy": "accept" ++ } ++ }, ++ { ++ "table": { ++ "family": "netdev", ++ "name": "filter3", ++ "handle": 0 ++ } ++ }, ++ { ++ "chain": { ++ "family": "netdev", ++ "table": "filter3", ++ "name": "Main_Ingress3", ++ "handle": 0, ++ "dev": [ ++ "d23456789012345", ++ "lo" ++ ], ++ "type": "filter", ++ "hook": "ingress", ++ "prio": -500, ++ "policy": "accept" ++ } ++ }, ++ { ++ "chain": { ++ "family": "netdev", ++ "table": "filter3", ++ "name": "Main_Egress3", ++ "handle": 0, ++ "dev": "lo", ++ "type": "filter", ++ "hook": "egress", ++ "prio": -500, ++ "policy": "accept" ++ } ++ } ++ ] ++} diff --git a/0225-tests-shell-Fix-ifname_based_hooks-feature-check.patch b/0225-tests-shell-Fix-ifname_based_hooks-feature-check.patch new file mode 100644 index 0000000..6b79fa3 --- /dev/null +++ b/0225-tests-shell-Fix-ifname_based_hooks-feature-check.patch @@ -0,0 +1,83 @@ +From a162e24506fe30a4fee1cb5738dd1a3371f883ba Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:14:04 +0200 +Subject: [PATCH] tests: shell: Fix ifname_based_hooks feature check + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 646acfaceb1f550c982c31ba6e60996b0bb012d7 + +commit 646acfaceb1f550c982c31ba6e60996b0bb012d7 +Author: Phil Sutter +Date: Wed Jun 25 18:53:36 2025 +0200 + + tests: shell: Fix ifname_based_hooks feature check + + The test was technically incorrect: Instead of detecting whether + interface hooks are name-based or not, it actually tested whether + netdev-family chains are removed along with their last hook. + + Since the latter behaviour is established in kernel commit fc0133428e7a + ("netfilter: nf_tables: Tolerate chains with no remaining hooks") and + thus independent from the name-based hooks change, treating both as the + same kernel feature is not acceptable. + + Fix this by detecting whether a netdev-family chain may be added despite + specifying a non-existent interface to hook into. Keep the old check + around with a better name, although unused for now. + + Reported-by: Florian Westphal + Fixes: f27e5abd81f29 ("tests: shell: Adjust to ifname-based hooks") + Signed-off-by: Phil Sutter + Signed-off-by: Florian Westphal + +Signed-off-by: Phil Sutter +--- + tests/shell/features/empty_netdev_chains.sh | 12 ++++++++++++ + tests/shell/features/ifname_based_hooks.sh | 18 +++++++++--------- + 2 files changed, 21 insertions(+), 9 deletions(-) + create mode 100755 tests/shell/features/empty_netdev_chains.sh + +diff --git a/tests/shell/features/empty_netdev_chains.sh b/tests/shell/features/empty_netdev_chains.sh +new file mode 100755 +index 0000000..cada695 +--- /dev/null ++++ b/tests/shell/features/empty_netdev_chains.sh +@@ -0,0 +1,12 @@ ++#!/bin/bash ++ ++# check if netdev chains survive without a single device ++ ++unshare -n bash -c "ip link add d0 type dummy; \ ++ $NFT \"table netdev t { \ ++ chain c { \ ++ type filter hook ingress priority 0; devices = { d0 }; \ ++ }; \ ++ }\"; \ ++ ip link del d0; \ ++ $NFT list chain netdev t c" +diff --git a/tests/shell/features/ifname_based_hooks.sh b/tests/shell/features/ifname_based_hooks.sh +index cada695..1f6af53 100755 +--- a/tests/shell/features/ifname_based_hooks.sh ++++ b/tests/shell/features/ifname_based_hooks.sh +@@ -1,12 +1,12 @@ + #!/bin/bash + +-# check if netdev chains survive without a single device ++# check if adding a netdev-family chain hooking into a non-existent device is ++# accepted or not + +-unshare -n bash -c "ip link add d0 type dummy; \ +- $NFT \"table netdev t { \ +- chain c { \ +- type filter hook ingress priority 0; devices = { d0 }; \ +- }; \ +- }\"; \ +- ip link del d0; \ +- $NFT list chain netdev t c" ++RULESET="table netdev t { ++ chain c { ++ type filter hook ingress priority 0 ++ devices = { foobar123 } ++ } ++}" ++unshare -n $NFT -f - <<< "$RULESET" diff --git a/0226-fib-allow-to-check-if-route-exists-in-maps.patch b/0226-fib-allow-to-check-if-route-exists-in-maps.patch new file mode 100644 index 0000000..55498d8 --- /dev/null +++ b/0226-fib-allow-to-check-if-route-exists-in-maps.patch @@ -0,0 +1,449 @@ +From 3dd05439f8513be1e3d783110a63fe444cf0ffaa Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:14:04 +0200 +Subject: [PATCH] fib: allow to check if route exists in maps + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit f4b646032acff4d743ad4f734aaca68e9264bdbb +Conflicts: Dropped changes to non-existent .json-nft dumps + +commit f4b646032acff4d743ad4f734aaca68e9264bdbb +Author: Pablo Neira Ayuso +Date: Tue Jun 24 18:11:06 2025 +0200 + + fib: allow to check if route exists in maps + + f686a17eafa0 ("fib: Support existence check") adds EXPR_F_BOOLEAN as a + workaround to infer from the rhs of the relational expression if the fib + lookup wants to check for a specific output interface or, instead, + simply check for existence. This, however, does not work with maps. + + The NFT_FIB_F_PRESENT flag can be used both with NFT_FIB_RESULT_OIF and + NFT_FIB_RESULT_OFINAME, my understanding is that they serve the same + purpose which is to check if a route exists, so they are redundant. + + Add a 'check' fib result to check for routes while still keeping the + inference workaround for backward compatibility, but prefer the new + syntax in the listing. + + Update man nft(8) and tests/py. + + Fixes: f686a17eafa0 ("fib: Support existence check") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + doc/data-types.txt | 2 +- + doc/primary-expression.txt | 5 ++- + include/fib.h | 2 +- + src/fib.c | 12 +++++-- + src/json.c | 2 +- + src/parser_bison.y | 21 +++++++---- + src/parser_json.c | 12 +++++-- + src/scanner.l | 4 +++ + tests/py/inet/fib.t | 6 ++-- + tests/py/inet/fib.t.json | 35 +++++++++++++++++-- + tests/py/inet/fib.t.json.output | 6 ++-- + tests/py/inet/fib.t.payload | 8 ++++- + tests/shell/testcases/json/single_flag | 4 +-- + .../parsing/dumps/large_rule_pipe.nft | 2 +- + tests/shell/testcases/parsing/large_rule_pipe | 2 +- + tests/shell/testcases/transactions/0049huge_0 | 2 +- + .../transactions/dumps/0049huge_0.nft | 2 +- + 17 files changed, 98 insertions(+), 29 deletions(-) + +diff --git a/doc/data-types.txt b/doc/data-types.txt +index 6c0e2f9..46b0867 100644 +--- a/doc/data-types.txt ++++ b/doc/data-types.txt +@@ -166,7 +166,7 @@ Check TCP option header existence. + .Boolean specification + ---------------------- + # match if route exists +-filter input fib daddr . iif oif exists ++filter input fib daddr . iif check exists + + # match only non-fragmented packets in IPv6 traffic + filter input exthdr frag missing +diff --git a/doc/primary-expression.txt b/doc/primary-expression.txt +index 43fa40c..705707c 100644 +--- a/doc/primary-expression.txt ++++ b/doc/primary-expression.txt +@@ -306,7 +306,7 @@ FIB EXPRESSIONS + [verse] + *fib* 'FIB_TUPLE' 'FIB_RESULT' + 'FIB_TUPLE' := { *saddr* | *daddr*} [ *.* { *iif* | *oif* } *.* *mark* ] +-'FIB_RESULT' := { *oif* | *oifname* | *type* } ++'FIB_RESULT' := { *oif* | *oifname* | *check* | *type* } + + + A fib expression queries the fib (forwarding information base) to obtain information +@@ -349,6 +349,9 @@ address types can be shown with *nft* *describe* *fib_addrtype*. + |oif| + Output interface index| + iface_index ++|check| ++Output interface check| ++boolean + |oifname| + Output interface name| + ifname +diff --git a/include/fib.h b/include/fib.h +index 67edccf..07bb221 100644 +--- a/include/fib.h ++++ b/include/fib.h +@@ -3,7 +3,7 @@ + + #include + +-extern const char *fib_result_str(enum nft_fib_result result); ++extern const char *fib_result_str(const struct expr *expr); + extern struct expr *fib_expr_alloc(const struct location *loc, + unsigned int flags, + unsigned int result); +diff --git a/src/fib.c b/src/fib.c +index 5a7c117..e28c522 100644 +--- a/src/fib.c ++++ b/src/fib.c +@@ -53,8 +53,16 @@ const struct datatype fib_addr_type = { + .sym_tbl = &addrtype_tbl, + }; + +-const char *fib_result_str(enum nft_fib_result result) ++const char *fib_result_str(const struct expr *expr) + { ++ enum nft_fib_result result = expr->fib.result; ++ uint32_t flags = expr->fib.flags; ++ ++ /* Exception: check if route exists. */ ++ if (result == NFT_FIB_RESULT_OIF && ++ flags & NFTA_FIB_F_PRESENT) ++ return "check"; ++ + if (result <= NFT_FIB_RESULT_MAX) + return fib_result[result]; + +@@ -87,7 +95,7 @@ static void fib_expr_print(const struct expr *expr, struct output_ctx *octx) + if (flags) + nft_print(octx, "0x%x", flags); + +- nft_print(octx, " %s", fib_result_str(expr->fib.result)); ++ nft_print(octx, " %s", fib_result_str(expr)); + } + + static bool fib_expr_cmp(const struct expr *e1, const struct expr *e2) +diff --git a/src/json.c b/src/json.c +index 65fbbfa..121c11d 100644 +--- a/src/json.c ++++ b/src/json.c +@@ -945,7 +945,7 @@ json_t *fib_expr_json(const struct expr *expr, struct output_ctx *octx) + unsigned int flags = expr->fib.flags & ~NFTA_FIB_F_PRESENT; + json_t *root; + +- root = nft_json_pack("{s:s}", "result", fib_result_str(expr->fib.result)); ++ root = nft_json_pack("{s:s}", "result", fib_result_str(expr)); + + if (flags) { + json_t *tmp = json_array(); +diff --git a/src/parser_bison.y b/src/parser_bison.y +index cdc9809..c62d44f 100644 +--- a/src/parser_bison.y ++++ b/src/parser_bison.y +@@ -286,6 +286,7 @@ int nft_lex(void *, void *, void *); + %token UNDEFINE "undefine" + + %token FIB "fib" ++%token CHECK "check" + + %token SOCKET "socket" + %token TRANSPARENT "transparent" +@@ -4341,30 +4342,38 @@ primary_expr : symbol_expr { $$ = $1; } + + fib_expr : FIB fib_tuple fib_result close_scope_fib + { +- if (($2 & (NFTA_FIB_F_SADDR|NFTA_FIB_F_DADDR)) == 0) { ++ uint32_t flags = $2, result = $3; ++ ++ if (result == __NFT_FIB_RESULT_MAX) { ++ result = NFT_FIB_RESULT_OIF; ++ flags |= NFTA_FIB_F_PRESENT; ++ } ++ ++ if ((flags & (NFTA_FIB_F_SADDR|NFTA_FIB_F_DADDR)) == 0) { + erec_queue(error(&@2, "fib: need either saddr or daddr"), state->msgs); + YYERROR; + } + +- if (($2 & (NFTA_FIB_F_SADDR|NFTA_FIB_F_DADDR)) == +- (NFTA_FIB_F_SADDR|NFTA_FIB_F_DADDR)) { ++ if ((flags & (NFTA_FIB_F_SADDR|NFTA_FIB_F_DADDR)) == ++ (NFTA_FIB_F_SADDR|NFTA_FIB_F_DADDR)) { + erec_queue(error(&@2, "fib: saddr and daddr are mutually exclusive"), state->msgs); + YYERROR; + } + +- if (($2 & (NFTA_FIB_F_IIF|NFTA_FIB_F_OIF)) == +- (NFTA_FIB_F_IIF|NFTA_FIB_F_OIF)) { ++ if ((flags & (NFTA_FIB_F_IIF|NFTA_FIB_F_OIF)) == ++ (NFTA_FIB_F_IIF|NFTA_FIB_F_OIF)) { + erec_queue(error(&@2, "fib: iif and oif are mutually exclusive"), state->msgs); + YYERROR; + } + +- $$ = fib_expr_alloc(&@$, $2, $3); ++ $$ = fib_expr_alloc(&@$, flags, result); + } + ; + + fib_result : OIF { $$ =NFT_FIB_RESULT_OIF; } + | OIFNAME { $$ =NFT_FIB_RESULT_OIFNAME; } + | TYPE close_scope_type { $$ =NFT_FIB_RESULT_ADDRTYPE; } ++ | CHECK { $$ = __NFT_FIB_RESULT_MAX; } /* actually, NFT_FIB_F_PRESENT. */ + ; + + fib_flag : SADDR { $$ = NFTA_FIB_F_SADDR; } +diff --git a/src/parser_json.c b/src/parser_json.c +index 88bf20a..3a2f00c 100644 +--- a/src/parser_json.c ++++ b/src/parser_json.c +@@ -1145,6 +1145,7 @@ static struct expr *json_parse_fib_expr(struct json_ctx *ctx, + [NFT_FIB_RESULT_OIF] = "oif", + [NFT_FIB_RESULT_OIFNAME] = "oifname", + [NFT_FIB_RESULT_ADDRTYPE] = "type", ++ [__NFT_FIB_RESULT_MAX] = "check", /* Actually, NFT_FIB_F_PRESENT. */ + }; + enum nft_fib_result resultval = NFT_FIB_RESULT_UNSPEC; + const char *result; +@@ -1160,12 +1161,19 @@ static struct expr *json_parse_fib_expr(struct json_ctx *ctx, + break; + } + } +- if (resultval == NFT_FIB_RESULT_UNSPEC) { ++ switch (resultval) { ++ case NFT_FIB_RESULT_UNSPEC: + json_error(ctx, "Invalid fib result '%s'.", result); + return NULL; ++ case __NFT_FIB_RESULT_MAX: ++ resultval = NFT_FIB_RESULT_OIF; ++ flagval = NFTA_FIB_F_PRESENT; ++ break; ++ default: ++ break; + } + +- flagval = parse_flags_array(ctx, root, "flags", fib_flag_parse); ++ flagval |= parse_flags_array(ctx, root, "flags", fib_flag_parse); + if (flagval < 0) + return NULL; + +diff --git a/src/scanner.l b/src/scanner.l +index 28d6da3..893772c 100644 +--- a/src/scanner.l ++++ b/src/scanner.l +@@ -789,6 +789,10 @@ addrstring ({macaddr}|{ip4addr}|{ip6addr}) + + "fib" { scanner_push_start_cond(yyscanner, SCANSTATE_EXPR_FIB); return FIB; } + ++{ ++ "check" { return CHECK; } ++} ++ + "osf" { scanner_push_start_cond(yyscanner, SCANSTATE_EXPR_OSF); return OSF; } + + "synproxy" { scanner_push_start_cond(yyscanner, SCANSTATE_STMT_SYNPROXY); return SYNPROXY; } +diff --git a/tests/py/inet/fib.t b/tests/py/inet/fib.t +index dbe45d9..f9c03b3 100644 +--- a/tests/py/inet/fib.t ++++ b/tests/py/inet/fib.t +@@ -13,5 +13,7 @@ fib daddr . iif type local;ok + fib daddr . iif type vmap { blackhole : drop, prohibit : drop, unicast : accept };ok + fib daddr . oif type local;fail + +-fib daddr oif exists;ok +-fib daddr oif missing;ok ++fib daddr check missing;ok ++fib daddr oif exists;ok;fib daddr check exists ++ ++fib daddr check vmap { missing : drop, exists : accept };ok +diff --git a/tests/py/inet/fib.t.json b/tests/py/inet/fib.t.json +index c298915..c2e9d45 100644 +--- a/tests/py/inet/fib.t.json ++++ b/tests/py/inet/fib.t.json +@@ -103,7 +103,7 @@ + "flags": [ + "daddr" + ], +- "result": "oif" ++ "result": "check" + } + }, + "op": "==", +@@ -112,7 +112,7 @@ + } + ] + +-# fib daddr oif missing ++# fib daddr check missing + [ + { + "match": { +@@ -121,7 +121,7 @@ + "flags": [ + "daddr" + ], +- "result": "oif" ++ "result": "check" + } + }, + "op": "==", +@@ -130,3 +130,32 @@ + } + ] + ++# fib daddr check vmap { missing : drop, exists : accept } ++[ ++ { ++ "vmap": { ++ "data": { ++ "set": [ ++ [ ++ false, ++ { ++ "drop": null ++ } ++ ], ++ [ ++ true, ++ { ++ "accept": null ++ } ++ ] ++ ] ++ }, ++ "key": { ++ "fib": { ++ "flags": "daddr", ++ "result": "check" ++ } ++ } ++ } ++ } ++] +diff --git a/tests/py/inet/fib.t.json.output b/tests/py/inet/fib.t.json.output +index e21f1e7..e8d0166 100644 +--- a/tests/py/inet/fib.t.json.output ++++ b/tests/py/inet/fib.t.json.output +@@ -44,7 +44,7 @@ + "left": { + "fib": { + "flags": "daddr", +- "result": "oif" ++ "result": "check" + } + }, + "op": "==", +@@ -53,14 +53,14 @@ + } + ] + +-# fib daddr oif missing ++# fib daddr check missing + [ + { + "match": { + "left": { + "fib": { + "flags": "daddr", +- "result": "oif" ++ "result": "check" + } + }, + "op": "==", +diff --git a/tests/py/inet/fib.t.payload b/tests/py/inet/fib.t.payload +index 050857d..e09a260 100644 +--- a/tests/py/inet/fib.t.payload ++++ b/tests/py/inet/fib.t.payload +@@ -26,7 +26,13 @@ ip test-ip prerouting + [ fib daddr oif present => reg 1 ] + [ cmp eq reg 1 0x00000001 ] + +-# fib daddr oif missing ++# fib daddr check missing + ip test-ip prerouting + [ fib daddr oif present => reg 1 ] + [ cmp eq reg 1 0x00000000 ] ++ ++# fib daddr check vmap { missing : drop, exists : accept } ++ element 00000000 : drop 0 [end] element 00000001 : accept 0 [end] ++ip test-ip prerouting ++ [ fib daddr oif present => reg 1 ] ++ [ lookup reg 1 set __map%d dreg 0 ] +diff --git a/tests/shell/testcases/json/single_flag b/tests/shell/testcases/json/single_flag +index 43ae452..f0a608a 100755 +--- a/tests/shell/testcases/json/single_flag ++++ b/tests/shell/testcases/json/single_flag +@@ -79,10 +79,10 @@ back_n_forth "$STD_SET_2" "$JSON_SET_2" + + STD_FIB_1="table ip t { + chain c { +- fib saddr oif exists ++ fib saddr check exists + } + }" +-JSON_FIB_1='{"nftables": [{"table": {"family": "ip", "name": "t", "handle": 0}}, {"chain": {"family": "ip", "table": "t", "name": "c", "handle": 0}}, {"rule": {"family": "ip", "table": "t", "chain": "c", "handle": 0, "expr": [{"match": {"op": "==", "left": {"fib": {"result": "oif", "flags": "saddr"}}, "right": true}}]}}]}' ++JSON_FIB_1='{"nftables": [{"table": {"family": "ip", "name": "t", "handle": 0}}, {"chain": {"family": "ip", "table": "t", "name": "c", "handle": 0}}, {"rule": {"family": "ip", "table": "t", "chain": "c", "handle": 0, "expr": [{"match": {"op": "==", "left": {"fib": {"result": "check", "flags": "saddr"}}, "right": true}}]}}]}' + JSON_FIB_1_EQUIV=$(sed 's/\("flags":\) \([^}]*\)/\1 [\2]/' <<< "$JSON_FIB_1") + + STD_FIB_2=$(sed 's/\(fib saddr\)/\1 . iif/' <<< "$STD_FIB_1") +diff --git a/tests/shell/testcases/parsing/dumps/large_rule_pipe.nft b/tests/shell/testcases/parsing/dumps/large_rule_pipe.nft +index 1583275..c85a636 100644 +--- a/tests/shell/testcases/parsing/dumps/large_rule_pipe.nft ++++ b/tests/shell/testcases/parsing/dumps/large_rule_pipe.nft +@@ -240,7 +240,7 @@ table inet firewalld { + chain raw_PREROUTING { + type filter hook prerouting priority raw + 10; policy accept; + icmpv6 type { nd-router-advert, nd-neighbor-solicit } accept +- meta nfproto ipv6 fib saddr . iif oif missing drop ++ meta nfproto ipv6 fib saddr . iif check missing drop + jump raw_PREROUTING_ZONES_SOURCE + jump raw_PREROUTING_ZONES + } +diff --git a/tests/shell/testcases/parsing/large_rule_pipe b/tests/shell/testcases/parsing/large_rule_pipe +index b6760c0..4c4d62d 100755 +--- a/tests/shell/testcases/parsing/large_rule_pipe ++++ b/tests/shell/testcases/parsing/large_rule_pipe +@@ -246,7 +246,7 @@ table inet firewalld { + chain raw_PREROUTING { + type filter hook prerouting priority -290; policy accept; + icmpv6 type { nd-router-advert, nd-neighbor-solicit } accept +- meta nfproto ipv6 fib saddr . iif oif missing drop ++ meta nfproto ipv6 fib saddr . iif check missing drop + jump raw_PREROUTING_ZONES_SOURCE + jump raw_PREROUTING_ZONES + } +diff --git a/tests/shell/testcases/transactions/0049huge_0 b/tests/shell/testcases/transactions/0049huge_0 +index f66953c..90049b6 100755 +--- a/tests/shell/testcases/transactions/0049huge_0 ++++ b/tests/shell/testcases/transactions/0049huge_0 +@@ -47,7 +47,7 @@ fi + $NFT flush ruleset + + RULESET='{"nftables": [{"metainfo": {"json_schema_version": 1}}, {"add": {"table": {"family": "inet", "name": "firewalld"}}}, {"add": {"table": {"family": "ip", "name": "firewalld"}}}, {"add": {"table": {"family": "ip6", "name": "firewalld"}}}, +-{"add": {"chain": {"family": "inet", "table": "firewalld", "name": "raw_PREROUTING", "type": "filter", "hook": "prerouting", "prio": -290}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "raw_PREROUTING_ZONES"}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "raw_PREROUTING", "expr": [{"jump": {"target": "raw_PREROUTING_ZONES"}}]}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "mangle_PREROUTING", "type": "filter", "hook": "prerouting", "prio": -140}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "mangle_PREROUTING_ZONES"}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "mangle_PREROUTING", "expr": [{"jump": {"target": "mangle_PREROUTING_ZONES"}}]}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_PREROUTING", "type": "nat", "hook": "prerouting", "prio": -90}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_PREROUTING_ZONES"}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_PREROUTING", "expr": [{"jump": {"target": "nat_PREROUTING_ZONES"}}]}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_POSTROUTING", "type": "nat", "hook": "postrouting", "prio": 110}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_POSTROUTING_ZONES"}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_POSTROUTING", "expr": [{"jump": {"target": "nat_POSTROUTING_ZONES"}}]}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_PREROUTING", "type": "nat", "hook": "prerouting", "prio": -90}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_PREROUTING_ZONES"}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_PREROUTING", "expr": [{"jump": {"target": "nat_PREROUTING_ZONES"}}]}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_POSTROUTING", "type": "nat", "hook": "postrouting", "prio": 110}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_POSTROUTING_ZONES"}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_POSTROUTING", "expr": [{"jump": {"target": "nat_POSTROUTING_ZONES"}}]}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_INPUT", "type": "filter", "hook": "input", "prio": 10}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FORWARD", "type": "filter", "hook": "forward", "prio": 10}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_OUTPUT", "type": "filter", "hook": "output", "prio": 10}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_INPUT_ZONES"}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_INPUT", "expr": [{"match": {"left": {"ct": {"key": "state"}}, "op": "in", "right": {"set": ["established", "related"]}}}, {"accept": null}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_INPUT", "expr": [{"match": {"left": {"ct": {"key": "status"}}, "op": "in", "right": "dnat"}}, {"accept": null}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_INPUT", "expr": [{"match": {"left": {"meta": {"key": "iifname"}}, "op": "==", "right": "lo"}}, {"accept": null}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_INPUT", "expr": [{"jump": {"target": "filter_INPUT_ZONES"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_INPUT", "expr": [{"match": {"left": {"ct": {"key": "state"}}, "op": "in", "right": {"set": ["invalid"]}}}, {"drop": null}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_INPUT", "expr": [{"reject": {"type": "icmpx", "expr": "admin-prohibited"}}]}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FORWARD_IN_ZONES"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FORWARD_OUT_ZONES"}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FORWARD", "expr": [{"match": {"left": {"ct": {"key": "state"}}, "op": "in", "right": {"set": ["established", "related"]}}}, {"accept": null}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FORWARD", "expr": [{"match": {"left": {"ct": {"key": "status"}}, "op": "in", "right": "dnat"}}, {"accept": null}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FORWARD", "expr": [{"match": {"left": {"meta": {"key": "iifname"}}, "op": "==", "right": "lo"}}, {"accept": null}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FORWARD", "expr": [{"jump": {"target": "filter_FORWARD_IN_ZONES"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FORWARD", "expr": [{"jump": {"target": "filter_FORWARD_OUT_ZONES"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FORWARD", "expr": [{"match": {"left": {"ct": {"key": "state"}}, "op": "in", "right": {"set": ["invalid"]}}}, {"drop": null}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FORWARD", "expr": [{"reject": {"type": "icmpx", "expr": "admin-prohibited"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_OUTPUT", "expr": [{"match": {"left": {"meta": {"key": "oifname"}}, "op": "==", "right": "lo"}}, {"accept": null}]}}}, {"insert": {"rule": {"family": "inet", "table": "firewalld", "chain": "raw_PREROUTING", "expr": [{"match": {"left": {"meta": {"key": "nfproto"}}, "op": "==", "right": "ipv6"}}, {"match": {"left": {"fib": {"flags": ["saddr", "iif"], "result": "oif"}}, "op": "==", "right": false}}, {"drop": null}]}}}, {"insert": {"rule": {"family": "inet", "table": "firewalld", "chain": "raw_PREROUTING", "expr": [{"match": {"left": {"payload": {"protocol": "icmpv6", "field": "type"}}, "op": "==", "right": {"set": ["nd-router-advert", "nd-neighbor-solicit"]}}}, {"accept": null}]}}}, ++{"add": {"chain": {"family": "inet", "table": "firewalld", "name": "raw_PREROUTING", "type": "filter", "hook": "prerouting", "prio": -290}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "raw_PREROUTING_ZONES"}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "raw_PREROUTING", "expr": [{"jump": {"target": "raw_PREROUTING_ZONES"}}]}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "mangle_PREROUTING", "type": "filter", "hook": "prerouting", "prio": -140}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "mangle_PREROUTING_ZONES"}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "mangle_PREROUTING", "expr": [{"jump": {"target": "mangle_PREROUTING_ZONES"}}]}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_PREROUTING", "type": "nat", "hook": "prerouting", "prio": -90}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_PREROUTING_ZONES"}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_PREROUTING", "expr": [{"jump": {"target": "nat_PREROUTING_ZONES"}}]}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_POSTROUTING", "type": "nat", "hook": "postrouting", "prio": 110}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_POSTROUTING_ZONES"}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_POSTROUTING", "expr": [{"jump": {"target": "nat_POSTROUTING_ZONES"}}]}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_PREROUTING", "type": "nat", "hook": "prerouting", "prio": -90}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_PREROUTING_ZONES"}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_PREROUTING", "expr": [{"jump": {"target": "nat_PREROUTING_ZONES"}}]}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_POSTROUTING", "type": "nat", "hook": "postrouting", "prio": 110}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_POSTROUTING_ZONES"}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_POSTROUTING", "expr": [{"jump": {"target": "nat_POSTROUTING_ZONES"}}]}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_INPUT", "type": "filter", "hook": "input", "prio": 10}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FORWARD", "type": "filter", "hook": "forward", "prio": 10}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_OUTPUT", "type": "filter", "hook": "output", "prio": 10}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_INPUT_ZONES"}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_INPUT", "expr": [{"match": {"left": {"ct": {"key": "state"}}, "op": "in", "right": {"set": ["established", "related"]}}}, {"accept": null}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_INPUT", "expr": [{"match": {"left": {"ct": {"key": "status"}}, "op": "in", "right": "dnat"}}, {"accept": null}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_INPUT", "expr": [{"match": {"left": {"meta": {"key": "iifname"}}, "op": "==", "right": "lo"}}, {"accept": null}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_INPUT", "expr": [{"jump": {"target": "filter_INPUT_ZONES"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_INPUT", "expr": [{"match": {"left": {"ct": {"key": "state"}}, "op": "in", "right": {"set": ["invalid"]}}}, {"drop": null}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_INPUT", "expr": [{"reject": {"type": "icmpx", "expr": "admin-prohibited"}}]}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FORWARD_IN_ZONES"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FORWARD_OUT_ZONES"}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FORWARD", "expr": [{"match": {"left": {"ct": {"key": "state"}}, "op": "in", "right": {"set": ["established", "related"]}}}, {"accept": null}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FORWARD", "expr": [{"match": {"left": {"ct": {"key": "status"}}, "op": "in", "right": "dnat"}}, {"accept": null}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FORWARD", "expr": [{"match": {"left": {"meta": {"key": "iifname"}}, "op": "==", "right": "lo"}}, {"accept": null}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FORWARD", "expr": [{"jump": {"target": "filter_FORWARD_IN_ZONES"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FORWARD", "expr": [{"jump": {"target": "filter_FORWARD_OUT_ZONES"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FORWARD", "expr": [{"match": {"left": {"ct": {"key": "state"}}, "op": "in", "right": {"set": ["invalid"]}}}, {"drop": null}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FORWARD", "expr": [{"reject": {"type": "icmpx", "expr": "admin-prohibited"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_OUTPUT", "expr": [{"match": {"left": {"meta": {"key": "oifname"}}, "op": "==", "right": "lo"}}, {"accept": null}]}}}, {"insert": {"rule": {"family": "inet", "table": "firewalld", "chain": "raw_PREROUTING", "expr": [{"match": {"left": {"meta": {"key": "nfproto"}}, "op": "==", "right": "ipv6"}}, {"match": {"left": {"fib": {"flags": ["saddr", "iif"], "result": "check"}}, "op": "==", "right": false}}, {"drop": null}]}}}, {"insert": {"rule": {"family": "inet", "table": "firewalld", "chain": "raw_PREROUTING", "expr": [{"match": {"left": {"payload": {"protocol": "icmpv6", "field": "type"}}, "op": "==", "right": {"set": ["nd-router-advert", "nd-neighbor-solicit"]}}}, {"accept": null}]}}}, + {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_OUTPUT", "index": 0, "expr": [{"match": {"left": {"payload": {"protocol": "ip6", "field": "daddr"}}, "op": "==", "right": {"set": [{"prefix": {"addr": "::0.0.0.0", "len": 96}}, {"prefix": {"addr": "::ffff:0.0.0.0", "len": 96}}, {"prefix": {"addr": "2002:0000::", "len": 24}}, {"prefix": {"addr": "2002:0a00::", "len": 24}}, {"prefix": {"addr": "2002:7f00::", "len": 24}}, {"prefix": {"addr": "2002:ac10::", "len": 28}}, {"prefix": {"addr": "2002:c0a8::", "len": 32}}, {"prefix": {"addr": "2002:a9fe::", "len": 32}}, {"prefix": {"addr": "2002:e000::", "len": 19}}]}}}, {"reject": {"type": "icmpv6", "expr": "addr-unreachable"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FORWARD", "index": 2, "expr": [{"match": {"left": {"payload": {"protocol": "ip6", "field": "daddr"}}, "op": "==", "right": {"set": [{"prefix": {"addr": "::0.0.0.0", "len": 96}}, {"prefix": {"addr": "::ffff:0.0.0.0", "len": 96}}, {"prefix": {"addr": "2002:0000::", "len": 24}}, {"prefix": {"addr": "2002:0a00::", "len": 24}}, {"prefix": {"addr": "2002:7f00::", "len": 24}}, {"prefix": {"addr": "2002:ac10::", "len": 28}}, {"prefix": {"addr": "2002:c0a8::", "len": 32}}, {"prefix": {"addr": "2002:a9fe::", "len": 32}}, {"prefix": {"addr": "2002:e000::", "len": 19}}]}}}, {"reject": {"type": "icmpv6", "expr": "addr-unreachable"}}]}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "raw_PRE_public"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "raw_PRE_public_pre"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "raw_PRE_public_log"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "raw_PRE_public_deny"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "raw_PRE_public_allow"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "raw_PRE_public_post"}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "raw_PRE_public", "expr": [{"jump": {"target": "raw_PRE_public_pre"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "raw_PRE_public", "expr": [{"jump": {"target": "raw_PRE_public_log"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "raw_PRE_public", "expr": [{"jump": {"target": "raw_PRE_public_deny"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "raw_PRE_public", "expr": [{"jump": {"target": "raw_PRE_public_allow"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "raw_PRE_public", "expr": [{"jump": {"target": "raw_PRE_public_post"}}]}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_IN_public"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_IN_public_pre"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_IN_public_log"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_IN_public_deny"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_IN_public_allow"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_IN_public_post"}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_IN_public", "expr": [{"jump": {"target": "filter_IN_public_pre"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_IN_public", "expr": [{"jump": {"target": "filter_IN_public_log"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_IN_public", "expr": [{"jump": {"target": "filter_IN_public_deny"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_IN_public", "expr": [{"jump": {"target": "filter_IN_public_allow"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_IN_public", "expr": [{"jump": {"target": "filter_IN_public_post"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_IN_public_allow", "expr": [{"match": {"left": {"payload": {"protocol": "tcp", "field": "dport"}}, "op": "==", "right": 22}}, {"match": {"left": {"ct": {"key": "state"}}, "op": "in", "right": {"set": ["new", "untracked"]}}}, {"accept": null}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_IN_public_allow", "expr": [{"match": {"left": {"payload": {"protocol": "ip6", "field": "daddr"}}, "op": "==", "right": {"prefix": {"addr": "fe80::", "len": 64}}}}, {"match": {"left": {"payload": {"protocol": "udp", "field": "dport"}}, "op": "==", "right": 546}}, {"match": {"left": {"ct": {"key": "state"}}, "op": "in", "right": {"set": ["new", "untracked"]}}}, {"accept": null}]}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDI_public"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDI_public_pre"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDI_public_log"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDI_public_deny"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDI_public_allow"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDI_public_post"}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDI_public", "expr": [{"jump": {"target": "filter_FWDI_public_pre"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDI_public", "expr": [{"jump": {"target": "filter_FWDI_public_log"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDI_public", "expr": [{"jump": {"target": "filter_FWDI_public_deny"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDI_public", "expr": [{"jump": {"target": "filter_FWDI_public_allow"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDI_public", "expr": [{"jump": {"target": "filter_FWDI_public_post"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_IN_public", "index": 4, "expr": [{"match": {"left": {"meta": {"key": "l4proto"}}, "op": "==", "right": {"set": ["icmp", "icmpv6"]}}}, {"accept": null}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDI_public", "index": 4, "expr": [{"match": {"left": {"meta": {"key": "l4proto"}}, "op": "==", "right": {"set": ["icmp", "icmpv6"]}}}, {"accept": null}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "raw_PREROUTING_ZONES", "expr": [{"goto": {"target": "raw_PRE_public"}}]}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "mangle_PRE_public"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "mangle_PRE_public_pre"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "mangle_PRE_public_log"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "mangle_PRE_public_deny"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "mangle_PRE_public_allow"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "mangle_PRE_public_post"}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "mangle_PRE_public", "expr": [{"jump": {"target": "mangle_PRE_public_pre"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "mangle_PRE_public", "expr": [{"jump": {"target": "mangle_PRE_public_log"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "mangle_PRE_public", "expr": [{"jump": {"target": "mangle_PRE_public_deny"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "mangle_PRE_public", "expr": [{"jump": {"target": "mangle_PRE_public_allow"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "mangle_PRE_public", "expr": [{"jump": {"target": "mangle_PRE_public_post"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "mangle_PREROUTING_ZONES", "expr": [{"goto": {"target": "mangle_PRE_public"}}]}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_PRE_public"}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_PRE_public_pre"}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_PRE_public_log"}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_PRE_public_deny"}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_PRE_public_allow"}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_PRE_public_post"}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_PRE_public", "expr": [{"jump": {"target": "nat_PRE_public_pre"}}]}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_PRE_public", "expr": [{"jump": {"target": "nat_PRE_public_log"}}]}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_PRE_public", "expr": [{"jump": {"target": "nat_PRE_public_deny"}}]}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_PRE_public", "expr": [{"jump": {"target": "nat_PRE_public_allow"}}]}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_PRE_public", "expr": [{"jump": {"target": "nat_PRE_public_post"}}]}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_PRE_public"}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_PRE_public_pre"}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_PRE_public_log"}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_PRE_public_deny"}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_PRE_public_allow"}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_PRE_public_post"}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_PRE_public", "expr": [{"jump": {"target": "nat_PRE_public_pre"}}]}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_PRE_public", "expr": [{"jump": {"target": "nat_PRE_public_log"}}]}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_PRE_public", "expr": [{"jump": {"target": "nat_PRE_public_deny"}}]}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_PRE_public", "expr": [{"jump": {"target": "nat_PRE_public_allow"}}]}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_PRE_public", "expr": [{"jump": {"target": "nat_PRE_public_post"}}]}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_PREROUTING_ZONES", "expr": [{"goto": {"target": "nat_PRE_public"}}]}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_PREROUTING_ZONES", "expr": [{"goto": {"target": "nat_PRE_public"}}]}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_POST_public"}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_POST_public_pre"}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_POST_public_log"}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_POST_public_deny"}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_POST_public_allow"}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_POST_public_post"}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_POST_public", "expr": [{"jump": {"target": "nat_POST_public_pre"}}]}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_POST_public", "expr": [{"jump": {"target": "nat_POST_public_log"}}]}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_POST_public", "expr": [{"jump": {"target": "nat_POST_public_deny"}}]}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_POST_public", "expr": [{"jump": {"target": "nat_POST_public_allow"}}]}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_POST_public", "expr": [{"jump": {"target": "nat_POST_public_post"}}]}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_POST_public"}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_POST_public_pre"}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_POST_public_log"}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_POST_public_deny"}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_POST_public_allow"}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_POST_public_post"}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_POST_public", "expr": [{"jump": {"target": "nat_POST_public_pre"}}]}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_POST_public", "expr": [{"jump": {"target": "nat_POST_public_log"}}]}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_POST_public", "expr": [{"jump": {"target": "nat_POST_public_deny"}}]}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_POST_public", "expr": [{"jump": {"target": "nat_POST_public_allow"}}]}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_POST_public", "expr": [{"jump": {"target": "nat_POST_public_post"}}]}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_POSTROUTING_ZONES", "expr": [{"goto": {"target": "nat_POST_public"}}]}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_POSTROUTING_ZONES", "expr": [{"goto": {"target": "nat_POST_public"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_INPUT_ZONES", "expr": [{"goto": {"target": "filter_IN_public"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FORWARD_IN_ZONES", "expr": [{"goto": {"target": "filter_FWDI_public"}}]}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDO_public"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDO_public_pre"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDO_public_log"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDO_public_deny"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDO_public_allow"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDO_public_post"}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDO_public", "expr": [{"jump": {"target": "filter_FWDO_public_pre"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDO_public", "expr": [{"jump": {"target": "filter_FWDO_public_log"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDO_public", "expr": [{"jump": {"target": "filter_FWDO_public_deny"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDO_public", "expr": [{"jump": {"target": "filter_FWDO_public_allow"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDO_public", "expr": [{"jump": {"target": "filter_FWDO_public_post"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FORWARD_OUT_ZONES", "expr": [{"goto": {"target": "filter_FWDO_public"}}]}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "raw_PRE_trusted"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "raw_PRE_trusted_pre"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "raw_PRE_trusted_log"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "raw_PRE_trusted_deny"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "raw_PRE_trusted_allow"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "raw_PRE_trusted_post"}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "raw_PRE_trusted", "expr": [{"jump": {"target": "raw_PRE_trusted_pre"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "raw_PRE_trusted", "expr": [{"jump": {"target": "raw_PRE_trusted_log"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "raw_PRE_trusted", "expr": [{"jump": {"target": "raw_PRE_trusted_deny"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "raw_PRE_trusted", "expr": [{"jump": {"target": "raw_PRE_trusted_allow"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "raw_PRE_trusted", "expr": [{"jump": {"target": "raw_PRE_trusted_post"}}]}}}, {"insert": {"rule": {"family": "inet", "table": "firewalld", "chain": "raw_PREROUTING_ZONES", "expr": [{"match": {"left": {"meta": {"key": "iifname"}}, "op": "==", "right": "perm_dummy2"}}, {"goto": {"target": "raw_PRE_trusted"}}]}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "mangle_PRE_trusted"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "mangle_PRE_trusted_pre"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "mangle_PRE_trusted_log"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "mangle_PRE_trusted_deny"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "mangle_PRE_trusted_allow"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "mangle_PRE_trusted_post"}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "mangle_PRE_trusted", "expr": [{"jump": {"target": "mangle_PRE_trusted_pre"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "mangle_PRE_trusted", "expr": [{"jump": {"target": "mangle_PRE_trusted_log"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "mangle_PRE_trusted", "expr": [{"jump": {"target": "mangle_PRE_trusted_deny"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "mangle_PRE_trusted", "expr": [{"jump": {"target": "mangle_PRE_trusted_allow"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "mangle_PRE_trusted", "expr": [{"jump": {"target": "mangle_PRE_trusted_post"}}]}}}, {"insert": {"rule": {"family": "inet", "table": "firewalld", "chain": "mangle_PREROUTING_ZONES", "expr": [{"match": {"left": {"meta": {"key": "iifname"}}, "op": "==", "right": "perm_dummy2"}}, {"goto": {"target": "mangle_PRE_trusted"}}]}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_PRE_trusted"}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_PRE_trusted_pre"}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_PRE_trusted_log"}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_PRE_trusted_deny"}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_PRE_trusted_allow"}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_PRE_trusted_post"}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_PRE_trusted", "expr": [{"jump": {"target": "nat_PRE_trusted_pre"}}]}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_PRE_trusted", "expr": [{"jump": {"target": "nat_PRE_trusted_log"}}]}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_PRE_trusted", "expr": [{"jump": {"target": "nat_PRE_trusted_deny"}}]}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_PRE_trusted", "expr": [{"jump": {"target": "nat_PRE_trusted_allow"}}]}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_PRE_trusted", "expr": [{"jump": {"target": "nat_PRE_trusted_post"}}]}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_PRE_trusted"}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_PRE_trusted_pre"}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_PRE_trusted_log"}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_PRE_trusted_deny"}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_PRE_trusted_allow"}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_PRE_trusted_post"}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_PRE_trusted", "expr": [{"jump": {"target": "nat_PRE_trusted_pre"}}]}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_PRE_trusted", "expr": [{"jump": {"target": "nat_PRE_trusted_log"}}]}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_PRE_trusted", "expr": [{"jump": {"target": "nat_PRE_trusted_deny"}}]}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_PRE_trusted", "expr": [{"jump": {"target": "nat_PRE_trusted_allow"}}]}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_PRE_trusted", "expr": [{"jump": {"target": "nat_PRE_trusted_post"}}]}}}, {"insert": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_PREROUTING_ZONES", "expr": [{"match": {"left": {"meta": {"key": "iifname"}}, "op": "==", "right": "perm_dummy2"}}, {"goto": {"target": "nat_PRE_trusted"}}]}}}, {"insert": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_PREROUTING_ZONES", "expr": [{"match": {"left": {"meta": {"key": "iifname"}}, "op": "==", "right": "perm_dummy2"}}, {"goto": {"target": "nat_PRE_trusted"}}]}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_POST_trusted"}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_POST_trusted_pre"}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_POST_trusted_log"}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_POST_trusted_deny"}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_POST_trusted_allow"}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_POST_trusted_post"}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_POST_trusted", "expr": [{"jump": {"target": "nat_POST_trusted_pre"}}]}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_POST_trusted", "expr": [{"jump": {"target": "nat_POST_trusted_log"}}]}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_POST_trusted", "expr": [{"jump": {"target": "nat_POST_trusted_deny"}}]}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_POST_trusted", "expr": [{"jump": {"target": "nat_POST_trusted_allow"}}]}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_POST_trusted", "expr": [{"jump": {"target": "nat_POST_trusted_post"}}]}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_POST_trusted"}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_POST_trusted_pre"}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_POST_trusted_log"}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_POST_trusted_deny"}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_POST_trusted_allow"}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_POST_trusted_post"}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_POST_trusted", "expr": [{"jump": {"target": "nat_POST_trusted_pre"}}]}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_POST_trusted", "expr": [{"jump": {"target": "nat_POST_trusted_log"}}]}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_POST_trusted", "expr": [{"jump": {"target": "nat_POST_trusted_deny"}}]}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_POST_trusted", "expr": [{"jump": {"target": "nat_POST_trusted_allow"}}]}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_POST_trusted", "expr": [{"jump": {"target": "nat_POST_trusted_post"}}]}}}, {"insert": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_POSTROUTING_ZONES", "expr": [{"match": {"left": {"meta": {"key": "oifname"}}, "op": "==", "right": "perm_dummy2"}}, {"goto": {"target": "nat_POST_trusted"}}]}}}, {"insert": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_POSTROUTING_ZONES", "expr": [{"match": {"left": {"meta": {"key": "oifname"}}, "op": "==", "right": "perm_dummy2"}}, {"goto": {"target": "nat_POST_trusted"}}]}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_IN_trusted"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_IN_trusted_pre"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_IN_trusted_log"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_IN_trusted_deny"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_IN_trusted_allow"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_IN_trusted_post"}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_IN_trusted", "expr": [{"jump": {"target": "filter_IN_trusted_pre"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_IN_trusted", "expr": [{"jump": {"target": "filter_IN_trusted_log"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_IN_trusted", "expr": [{"jump": {"target": "filter_IN_trusted_deny"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_IN_trusted", "expr": [{"jump": {"target": "filter_IN_trusted_allow"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_IN_trusted", "expr": [{"jump": {"target": "filter_IN_trusted_post"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_IN_trusted", "expr": [{"accept": null}]}}}, {"insert": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_INPUT_ZONES", "expr": [{"match": {"left": {"meta": {"key": "iifname"}}, "op": "==", "right": "perm_dummy2"}}, {"goto": {"target": "filter_IN_trusted"}}]}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDI_trusted"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDI_trusted_pre"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDI_trusted_log"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDI_trusted_deny"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDI_trusted_allow"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDI_trusted_post"}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDI_trusted", "expr": [{"jump": {"target": "filter_FWDI_trusted_pre"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDI_trusted", "expr": [{"jump": {"target": "filter_FWDI_trusted_log"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDI_trusted", "expr": [{"jump": {"target": "filter_FWDI_trusted_deny"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDI_trusted", "expr": [{"jump": {"target": "filter_FWDI_trusted_allow"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDI_trusted", "expr": [{"jump": {"target": "filter_FWDI_trusted_post"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDI_trusted", "expr": [{"accept": null}]}}}, {"insert": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FORWARD_IN_ZONES", "expr": [{"match": {"left": {"meta": {"key": "iifname"}}, "op": "==", "right": "perm_dummy2"}}, {"goto": {"target": "filter_FWDI_trusted"}}]}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDO_trusted"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDO_trusted_pre"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDO_trusted_log"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDO_trusted_deny"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDO_trusted_allow"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDO_trusted_post"}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDO_trusted", "expr": [{"jump": {"target": "filter_FWDO_trusted_pre"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDO_trusted", "expr": [{"jump": {"target": "filter_FWDO_trusted_log"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDO_trusted", "expr": [{"jump": {"target": "filter_FWDO_trusted_deny"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDO_trusted", "expr": [{"jump": {"target": "filter_FWDO_trusted_allow"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDO_trusted", "expr": [{"jump": {"target": "filter_FWDO_trusted_post"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDO_trusted", "expr": [{"accept": null}]}}}, {"insert": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FORWARD_OUT_ZONES", "expr": [{"match": {"left": {"meta": {"key": "oifname"}}, "op": "==", "right": "perm_dummy2"}}, {"goto": {"target": "filter_FWDO_trusted"}}]}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "raw_PRE_work"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "raw_PRE_work_pre"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "raw_PRE_work_log"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "raw_PRE_work_deny"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "raw_PRE_work_allow"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "raw_PRE_work_post"}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "raw_PRE_work", "expr": [{"jump": {"target": "raw_PRE_work_pre"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "raw_PRE_work", "expr": [{"jump": {"target": "raw_PRE_work_log"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "raw_PRE_work", "expr": [{"jump": {"target": "raw_PRE_work_deny"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "raw_PRE_work", "expr": [{"jump": {"target": "raw_PRE_work_allow"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "raw_PRE_work", "expr": [{"jump": {"target": "raw_PRE_work_post"}}]}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_IN_work"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_IN_work_pre"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_IN_work_log"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_IN_work_deny"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_IN_work_allow"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_IN_work_post"}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_IN_work", "expr": [{"jump": {"target": "filter_IN_work_pre"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_IN_work", "expr": [{"jump": {"target": "filter_IN_work_log"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_IN_work", "expr": [{"jump": {"target": "filter_IN_work_deny"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_IN_work", "expr": [{"jump": {"target": "filter_IN_work_allow"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_IN_work", "expr": [{"jump": {"target": "filter_IN_work_post"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_IN_work_allow", "expr": [{"match": {"left": {"payload": {"protocol": "tcp", "field": "dport"}}, "op": "==", "right": 22}}, {"match": {"left": {"ct": {"key": "state"}}, "op": "in", "right": {"set": ["new", "untracked"]}}}, {"accept": null}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_IN_work_allow", "expr": [{"match": {"left": {"payload": {"protocol": "ip6", "field": "daddr"}}, "op": "==", "right": {"prefix": {"addr": "fe80::", "len": 64}}}}, {"match": {"left": {"payload": {"protocol": "udp", "field": "dport"}}, "op": "==", "right": 546}}, {"match": {"left": {"ct": {"key": "state"}}, "op": "in", "right": {"set": ["new", "untracked"]}}}, {"accept": null}]}}}, {"insert": {"rule": {"family": "inet", "table": "firewalld", "chain": "raw_PREROUTING_ZONES", "expr": [{"match": {"left": {"meta": {"key": "iifname"}}, "op": "==", "right": "perm_dummy"}}, {"goto": {"target": "raw_PRE_work"}}]}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "mangle_PRE_work"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "mangle_PRE_work_pre"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "mangle_PRE_work_log"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "mangle_PRE_work_deny"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "mangle_PRE_work_allow"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "mangle_PRE_work_post"}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "mangle_PRE_work", "expr": [{"jump": {"target": "mangle_PRE_work_pre"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "mangle_PRE_work", "expr": [{"jump": {"target": "mangle_PRE_work_log"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "mangle_PRE_work", "expr": [{"jump": {"target": "mangle_PRE_work_deny"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "mangle_PRE_work", "expr": [{"jump": {"target": "mangle_PRE_work_allow"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "mangle_PRE_work", "expr": [{"jump": {"target": "mangle_PRE_work_post"}}]}}}, {"insert": {"rule": {"family": "inet", "table": "firewalld", "chain": "mangle_PREROUTING_ZONES", "expr": [{"match": {"left": {"meta": {"key": "iifname"}}, "op": "==", "right": "perm_dummy"}}, {"goto": {"target": "mangle_PRE_work"}}]}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_PRE_work"}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_PRE_work_pre"}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_PRE_work_log"}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_PRE_work_deny"}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_PRE_work_allow"}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_PRE_work_post"}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_PRE_work", "expr": [{"jump": {"target": "nat_PRE_work_pre"}}]}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_PRE_work", "expr": [{"jump": {"target": "nat_PRE_work_log"}}]}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_PRE_work", "expr": [{"jump": {"target": "nat_PRE_work_deny"}}]}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_PRE_work", "expr": [{"jump": {"target": "nat_PRE_work_allow"}}]}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_PRE_work", "expr": [{"jump": {"target": "nat_PRE_work_post"}}]}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_PRE_work"}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_PRE_work_pre"}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_PRE_work_log"}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_PRE_work_deny"}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_PRE_work_allow"}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_PRE_work_post"}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_PRE_work", "expr": [{"jump": {"target": "nat_PRE_work_pre"}}]}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_PRE_work", "expr": [{"jump": {"target": "nat_PRE_work_log"}}]}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_PRE_work", "expr": [{"jump": {"target": "nat_PRE_work_deny"}}]}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_PRE_work", "expr": [{"jump": {"target": "nat_PRE_work_allow"}}]}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_PRE_work", "expr": [{"jump": {"target": "nat_PRE_work_post"}}]}}}, {"insert": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_PREROUTING_ZONES", "expr": [{"match": {"left": {"meta": {"key": "iifname"}}, "op": "==", "right": "perm_dummy"}}, {"goto": {"target": "nat_PRE_work"}}]}}}, {"insert": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_PREROUTING_ZONES", "expr": [{"match": {"left": {"meta": {"key": "iifname"}}, "op": "==", "right": "perm_dummy"}}, {"goto": {"target": "nat_PRE_work"}}]}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_POST_work"}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_POST_work_pre"}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_POST_work_log"}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_POST_work_deny"}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_POST_work_allow"}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_POST_work_post"}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_POST_work", "expr": [{"jump": {"target": "nat_POST_work_pre"}}]}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_POST_work", "expr": [{"jump": {"target": "nat_POST_work_log"}}]}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_POST_work", "expr": [{"jump": {"target": "nat_POST_work_deny"}}]}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_POST_work", "expr": [{"jump": {"target": "nat_POST_work_allow"}}]}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_POST_work", "expr": [{"jump": {"target": "nat_POST_work_post"}}]}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_POST_work"}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_POST_work_pre"}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_POST_work_log"}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_POST_work_deny"}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_POST_work_allow"}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_POST_work_post"}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_POST_work", "expr": [{"jump": {"target": "nat_POST_work_pre"}}]}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_POST_work", "expr": [{"jump": {"target": "nat_POST_work_log"}}]}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_POST_work", "expr": [{"jump": {"target": "nat_POST_work_deny"}}]}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_POST_work", "expr": [{"jump": {"target": "nat_POST_work_allow"}}]}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_POST_work", "expr": [{"jump": {"target": "nat_POST_work_post"}}]}}}, {"insert": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_POSTROUTING_ZONES", "expr": [{"match": {"left": {"meta": {"key": "oifname"}}, "op": "==", "right": "perm_dummy"}}, {"goto": {"target": "nat_POST_work"}}]}}}, {"insert": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_POSTROUTING_ZONES", "expr": [{"match": {"left": {"meta": {"key": "oifname"}}, "op": "==", "right": "perm_dummy"}}, {"goto": {"target": "nat_POST_work"}}]}}}, {"insert": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_INPUT_ZONES", "expr": [{"match": {"left": {"meta": {"key": "iifname"}}, "op": "==", "right": "perm_dummy"}}, {"goto": {"target": "filter_IN_work"}}]}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDI_work"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDI_work_pre"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDI_work_log"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDI_work_deny"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDI_work_allow"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDI_work_post"}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDI_work", "expr": [{"jump": {"target": "filter_FWDI_work_pre"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDI_work", "expr": [{"jump": {"target": "filter_FWDI_work_log"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDI_work", "expr": [{"jump": {"target": "filter_FWDI_work_deny"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDI_work", "expr": [{"jump": {"target": "filter_FWDI_work_allow"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDI_work", "expr": [{"jump": {"target": "filter_FWDI_work_post"}}]}}}, {"insert": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FORWARD_IN_ZONES", "expr": [{"match": {"left": {"meta": {"key": "iifname"}}, "op": "==", "right": "perm_dummy"}}, {"goto": {"target": "filter_FWDI_work"}}]}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDO_work"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDO_work_pre"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDO_work_log"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDO_work_deny"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDO_work_allow"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDO_work_post"}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDO_work", "expr": [{"jump": {"target": "filter_FWDO_work_pre"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDO_work", "expr": [{"jump": {"target": "filter_FWDO_work_log"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDO_work", "expr": [{"jump": {"target": "filter_FWDO_work_deny"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDO_work", "expr": [{"jump": {"target": "filter_FWDO_work_allow"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDO_work", "expr": [{"jump": {"target": "filter_FWDO_work_post"}}]}}}, {"insert": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FORWARD_OUT_ZONES", "expr": [{"match": {"left": {"meta": {"key": "oifname"}}, "op": "==", "right": "perm_dummy"}}, {"goto": {"target": "filter_FWDO_work"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_IN_work", "index": 4, "expr": [{"match": {"left": {"meta": {"key": "l4proto"}}, "op": "==", "right": {"set": ["icmp", "icmpv6"]}}}, {"accept": null}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDI_work", "index": 4, "expr": [{"match": {"left": {"meta": {"key": "l4proto"}}, "op": "==", "right": {"set": ["icmp", "icmpv6"]}}}, {"accept": null}]}}}]}' + + if [ "$NFT_TEST_HAVE_json" != n ]; then +diff --git a/tests/shell/testcases/transactions/dumps/0049huge_0.nft b/tests/shell/testcases/transactions/dumps/0049huge_0.nft +index 96f5a38..e42ad3e 100644 +--- a/tests/shell/testcases/transactions/dumps/0049huge_0.nft ++++ b/tests/shell/testcases/transactions/dumps/0049huge_0.nft +@@ -2,7 +2,7 @@ table inet firewalld { + chain raw_PREROUTING { + type filter hook prerouting priority raw + 10; policy accept; + icmpv6 type { nd-router-advert, nd-neighbor-solicit } accept +- meta nfproto ipv6 fib saddr . iif oif missing drop ++ meta nfproto ipv6 fib saddr . iif check missing drop + jump raw_PREROUTING_ZONES + } + diff --git a/0227-fib-allow-to-use-it-in-set-statements.patch b/0227-fib-allow-to-use-it-in-set-statements.patch new file mode 100644 index 0000000..3735e5f --- /dev/null +++ b/0227-fib-allow-to-use-it-in-set-statements.patch @@ -0,0 +1,134 @@ +From 965035f7ecc717e5344f8ee1264e3c645f3061fc Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:14:18 +0200 +Subject: [PATCH] fib: allow to use it in set statements + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 525b58568dca5ab9998595fc45313eac2764b6b1 + +commit 525b58568dca5ab9998595fc45313eac2764b6b1 +Author: Pablo Neira Ayuso +Date: Tue Jun 24 18:11:10 2025 +0200 + + fib: allow to use it in set statements + + Allow to use fib expression in set statements, eg. + + meta mark set ip saddr . fib daddr check map { 1.2.3.4 . exists : 0x00000001 } + + Fixes: 4a75ed32132d ("src: add fib expression") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/parser_bison.y | 1 + + src/parser_json.c | 2 +- + tests/py/inet/fib.t | 2 ++ + tests/py/inet/fib.t.json | 45 +++++++++++++++++++++++++++++++++++++ + tests/py/inet/fib.t.payload | 8 +++++++ + 5 files changed, 57 insertions(+), 1 deletion(-) + +diff --git a/src/parser_bison.y b/src/parser_bison.y +index c62d44f..62cd895 100644 +--- a/src/parser_bison.y ++++ b/src/parser_bison.y +@@ -3837,6 +3837,7 @@ primary_stmt_expr : symbol_expr { $$ = $1; } + | payload_expr { $$ = $1; } + | keyword_expr { $$ = $1; } + | socket_expr { $$ = $1; } ++ | fib_expr { $$ = $1; } + | osf_expr { $$ = $1; } + | '(' basic_stmt_expr ')' { $$ = $2; } + ; +diff --git a/src/parser_json.c b/src/parser_json.c +index 3a2f00c..6cefb39 100644 +--- a/src/parser_json.c ++++ b/src/parser_json.c +@@ -1591,7 +1591,7 @@ static struct expr *json_parse_expr(struct json_ctx *ctx, json_t *root) + /* below two are hash expr */ + { "jhash", json_parse_hash_expr, CTX_F_STMT | CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_SES | CTX_F_MAP | CTX_F_CONCAT }, + { "symhash", json_parse_hash_expr, CTX_F_STMT | CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_SES | CTX_F_MAP | CTX_F_CONCAT }, +- { "fib", json_parse_fib_expr, CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_SES | CTX_F_MAP | CTX_F_CONCAT }, ++ { "fib", json_parse_fib_expr, CTX_F_STMT | CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_SES | CTX_F_MAP | CTX_F_CONCAT }, + { "|", json_parse_binop_expr, CTX_F_RHS | CTX_F_STMT | CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_SES | CTX_F_MAP | CTX_F_CONCAT }, + { "^", json_parse_binop_expr, CTX_F_RHS | CTX_F_STMT | CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_SES | CTX_F_MAP | CTX_F_CONCAT }, + { "&", json_parse_binop_expr, CTX_F_RHS | CTX_F_STMT | CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_SES | CTX_F_MAP | CTX_F_CONCAT }, +diff --git a/tests/py/inet/fib.t b/tests/py/inet/fib.t +index f9c03b3..60b77a4 100644 +--- a/tests/py/inet/fib.t ++++ b/tests/py/inet/fib.t +@@ -17,3 +17,5 @@ fib daddr check missing;ok + fib daddr oif exists;ok;fib daddr check exists + + fib daddr check vmap { missing : drop, exists : accept };ok ++ ++meta mark set fib daddr check . ct mark map { exists . 0x00000000 : 0x00000001 };ok +diff --git a/tests/py/inet/fib.t.json b/tests/py/inet/fib.t.json +index c2e9d45..14a6249 100644 +--- a/tests/py/inet/fib.t.json ++++ b/tests/py/inet/fib.t.json +@@ -159,3 +159,48 @@ + } + } + ] ++ ++# meta mark set fib daddr check . ct mark map { exists . 0x00000000 : 0x00000001 } ++[ ++ { ++ "mangle": { ++ "key": { ++ "meta": { ++ "key": "mark" ++ } ++ }, ++ "value": { ++ "map": { ++ "data": { ++ "set": [ ++ [ ++ { ++ "concat": [ ++ true, ++ 0 ++ ] ++ }, ++ 1 ++ ] ++ ] ++ }, ++ "key": { ++ "concat": [ ++ { ++ "fib": { ++ "flags": "daddr", ++ "result": "check" ++ } ++ }, ++ { ++ "ct": { ++ "key": "mark" ++ } ++ } ++ ] ++ } ++ } ++ } ++ } ++ } ++] +diff --git a/tests/py/inet/fib.t.payload b/tests/py/inet/fib.t.payload +index e09a260..02d92b5 100644 +--- a/tests/py/inet/fib.t.payload ++++ b/tests/py/inet/fib.t.payload +@@ -36,3 +36,11 @@ ip test-ip prerouting + ip test-ip prerouting + [ fib daddr oif present => reg 1 ] + [ lookup reg 1 set __map%d dreg 0 ] ++ ++# meta mark set fib daddr check . ct mark map { exists . 0x00000000 : 0x00000001 } ++ element 00000001 00000000 : 00000001 0 [end] ++ip test-ip prerouting ++ [ fib daddr oif present => reg 1 ] ++ [ ct load mark => reg 9 ] ++ [ lookup reg 1 set __map%d dreg 1 ] ++ [ meta set mark with reg 1 ] diff --git a/0228-tests-py-re-enables-nft-test.py-to-load-the-local-nf.patch b/0228-tests-py-re-enables-nft-test.py-to-load-the-local-nf.patch new file mode 100644 index 0000000..28ce8d2 --- /dev/null +++ b/0228-tests-py-re-enables-nft-test.py-to-load-the-local-nf.patch @@ -0,0 +1,45 @@ +From 5a5e88276a8fc1764ab934e10ef84d82abaf9cc7 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:14:18 +0200 +Subject: [PATCH] tests: py: re-enables nft-test.py to load the local + nftables.py + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 0f3965a38aecaf1fda66c2ce5dcf3b755d8191c6 + +commit 0f3965a38aecaf1fda66c2ce5dcf3b755d8191c6 +Author: Zhongqiu Duan +Date: Fri Jul 4 03:12:16 2025 +0000 + + tests: py: re-enables nft-test.py to load the local nftables.py + + This is a needed follow-up of commit ce443afc21455 ("py: move + package source into src directory") from 2023. Since that change, + nft-test.py started using the host's nftables.py instead of the local + one. But since nft-test.py passes the local src/.libs/libnftables.so.1 + as parameter when instantiating the Nftables class, we did nevertheless + use the local libnftables. + + Fixes: ce443afc21455 ("py: move package source into src directory") + Reviewed-by: Phil Sutter + Signed-off-by: Zhongqiu Duan + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + tests/py/nft-test.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/tests/py/nft-test.py b/tests/py/nft-test.py +index 376e62a..4799636 100755 +--- a/tests/py/nft-test.py ++++ b/tests/py/nft-test.py +@@ -23,7 +23,7 @@ import traceback + import tempfile + + TESTS_PATH = os.path.dirname(os.path.abspath(__file__)) +-sys.path.insert(0, os.path.join(TESTS_PATH, '../../py/')) ++sys.path.insert(0, os.path.join(TESTS_PATH, '../../py/src')) + os.environ['TZ'] = 'UTC-2' + + from nftables import Nftables diff --git a/0229-rule-print-chain-and-flowtable-devices-in-quotes.patch b/0229-rule-print-chain-and-flowtable-devices-in-quotes.patch new file mode 100644 index 0000000..d3595f4 --- /dev/null +++ b/0229-rule-print-chain-and-flowtable-devices-in-quotes.patch @@ -0,0 +1,263 @@ +From b92b3d36998beea4910dd8d69af90ffbd089f1d9 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:14:18 +0200 +Subject: [PATCH] rule: print chain and flowtable devices in quotes + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit eb30f236d91a8d61ece789e28e6540b3a3fa2a6a + +commit eb30f236d91a8d61ece789e28e6540b3a3fa2a6a +Author: Pablo Neira Ayuso +Date: Wed Jul 9 00:13:56 2025 +0200 + + rule: print chain and flowtable devices in quotes + + Print devices in quotes, for consistency with: + + - the existing chain listing with single device: + + type filter hook ingress device "lo" priority filter; policy accept + + - the ifname datatype used in sets. + + In general, tokens that are user-defined, not coming in the datatype + symbol list, are enclosed in quotes. + + Fixes: 3fdc7541fba0 ("src: add multidevice support for netdev chain") + Acked-by: Florian Westphal + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/rule.c | 4 ++-- + tests/shell/features/flowtable_counter.sh | 2 +- + .../testcases/chains/dumps/0042chain_variable_0.nft | 4 ++-- + .../testcases/flowtable/dumps/0001flowtable_0.nft | 2 +- + .../flowtable/dumps/0002create_flowtable_0.nft | 2 +- + .../flowtable/dumps/0003add_after_flush_0.nft | 2 +- + .../flowtable/dumps/0005delete_in_use_1.nft | 2 +- + .../flowtable/dumps/0012flowtable_variable_0.nft | 4 ++-- + .../flowtable/dumps/0013addafterdelete_0.nft | 2 +- + .../flowtable/dumps/0014addafterdelete_0.nft | 2 +- + tests/shell/testcases/listing/0020flowtable_0 | 12 ++++++------ + .../testcases/listing/dumps/0020flowtable_0.nft | 4 ++-- + 12 files changed, 21 insertions(+), 21 deletions(-) + +diff --git a/src/rule.c b/src/rule.c +index bfcf4a2..5826f11 100644 +--- a/src/rule.c ++++ b/src/rule.c +@@ -1057,7 +1057,7 @@ static void chain_print_declaration(const struct chain *chain, + } else if (chain->dev_array_len > 1) { + nft_print(octx, " devices = { "); + for (i = 0; i < chain->dev_array_len; i++) { +- nft_print(octx, "%s", chain->dev_array[i]); ++ nft_print(octx, "\"%s\"", chain->dev_array[i]); + if (i + 1 != chain->dev_array_len) + nft_print(octx, ", "); + } +@@ -2138,7 +2138,7 @@ static void flowtable_print_declaration(const struct flowtable *flowtable, + if (flowtable->dev_array_len > 0) { + nft_print(octx, "%s%sdevices = { ", opts->tab, opts->tab); + for (i = 0; i < flowtable->dev_array_len; i++) { +- nft_print(octx, "%s", flowtable->dev_array[i]); ++ nft_print(octx, "\"%s\"", flowtable->dev_array[i]); + if (i + 1 != flowtable->dev_array_len) + nft_print(octx, ", "); + } +diff --git a/tests/shell/features/flowtable_counter.sh b/tests/shell/features/flowtable_counter.sh +index a4c4c62..5d47215 100755 +--- a/tests/shell/features/flowtable_counter.sh ++++ b/tests/shell/features/flowtable_counter.sh +@@ -6,7 +6,7 @@ + EXPECTED="table ip filter2 { + flowtable main_ft2 { + hook ingress priority filter +- devices = { lo } ++ devices = { \"lo\" } + counter + } + }" +diff --git a/tests/shell/testcases/chains/dumps/0042chain_variable_0.nft b/tests/shell/testcases/chains/dumps/0042chain_variable_0.nft +index 84a908d..08a1901 100644 +--- a/tests/shell/testcases/chains/dumps/0042chain_variable_0.nft ++++ b/tests/shell/testcases/chains/dumps/0042chain_variable_0.nft +@@ -5,12 +5,12 @@ table netdev filter1 { + } + table netdev filter2 { + chain Main_Ingress2 { +- type filter hook ingress devices = { d23456789012345, lo } priority -500; policy accept; ++ type filter hook ingress devices = { "d23456789012345", "lo" } priority -500; policy accept; + } + } + table netdev filter3 { + chain Main_Ingress3 { +- type filter hook ingress devices = { d23456789012345, lo } priority -500; policy accept; ++ type filter hook ingress devices = { "d23456789012345", "lo" } priority -500; policy accept; + } + + chain Main_Egress3 { +diff --git a/tests/shell/testcases/flowtable/dumps/0001flowtable_0.nft b/tests/shell/testcases/flowtable/dumps/0001flowtable_0.nft +index 629bfe8..79fa591 100644 +--- a/tests/shell/testcases/flowtable/dumps/0001flowtable_0.nft ++++ b/tests/shell/testcases/flowtable/dumps/0001flowtable_0.nft +@@ -1,7 +1,7 @@ + table inet t { + flowtable f { + hook ingress priority filter + 10 +- devices = { lo } ++ devices = { "lo" } + } + + chain c { +diff --git a/tests/shell/testcases/flowtable/dumps/0002create_flowtable_0.nft b/tests/shell/testcases/flowtable/dumps/0002create_flowtable_0.nft +index aecfb2a..2d0ea90 100644 +--- a/tests/shell/testcases/flowtable/dumps/0002create_flowtable_0.nft ++++ b/tests/shell/testcases/flowtable/dumps/0002create_flowtable_0.nft +@@ -1,6 +1,6 @@ + table ip t { + flowtable f { + hook ingress priority filter + 10 +- devices = { lo } ++ devices = { "lo" } + } + } +diff --git a/tests/shell/testcases/flowtable/dumps/0003add_after_flush_0.nft b/tests/shell/testcases/flowtable/dumps/0003add_after_flush_0.nft +index dd904f4..39de91b 100644 +--- a/tests/shell/testcases/flowtable/dumps/0003add_after_flush_0.nft ++++ b/tests/shell/testcases/flowtable/dumps/0003add_after_flush_0.nft +@@ -1,6 +1,6 @@ + table ip x { + flowtable y { + hook ingress priority filter +- devices = { lo } ++ devices = { "lo" } + } + } +diff --git a/tests/shell/testcases/flowtable/dumps/0005delete_in_use_1.nft b/tests/shell/testcases/flowtable/dumps/0005delete_in_use_1.nft +index c1d79e7..b01b302 100644 +--- a/tests/shell/testcases/flowtable/dumps/0005delete_in_use_1.nft ++++ b/tests/shell/testcases/flowtable/dumps/0005delete_in_use_1.nft +@@ -1,7 +1,7 @@ + table ip x { + flowtable y { + hook ingress priority filter +- devices = { lo } ++ devices = { "lo" } + } + + chain x { +diff --git a/tests/shell/testcases/flowtable/dumps/0012flowtable_variable_0.nft b/tests/shell/testcases/flowtable/dumps/0012flowtable_variable_0.nft +index df1c51a..7863822 100644 +--- a/tests/shell/testcases/flowtable/dumps/0012flowtable_variable_0.nft ++++ b/tests/shell/testcases/flowtable/dumps/0012flowtable_variable_0.nft +@@ -1,14 +1,14 @@ + table ip filter1 { + flowtable Main_ft1 { + hook ingress priority filter +- devices = { lo } ++ devices = { "lo" } + counter + } + } + table ip filter2 { + flowtable Main_ft2 { + hook ingress priority filter +- devices = { lo } ++ devices = { "lo" } + counter + } + } +diff --git a/tests/shell/testcases/flowtable/dumps/0013addafterdelete_0.nft b/tests/shell/testcases/flowtable/dumps/0013addafterdelete_0.nft +index 67db7d0..585f63b 100644 +--- a/tests/shell/testcases/flowtable/dumps/0013addafterdelete_0.nft ++++ b/tests/shell/testcases/flowtable/dumps/0013addafterdelete_0.nft +@@ -1,6 +1,6 @@ + table inet filter { + flowtable f { + hook ingress priority filter - 1 +- devices = { lo } ++ devices = { "lo" } + } + } +diff --git a/tests/shell/testcases/flowtable/dumps/0014addafterdelete_0.nft b/tests/shell/testcases/flowtable/dumps/0014addafterdelete_0.nft +index 145aa08..12f97a7 100644 +--- a/tests/shell/testcases/flowtable/dumps/0014addafterdelete_0.nft ++++ b/tests/shell/testcases/flowtable/dumps/0014addafterdelete_0.nft +@@ -1,7 +1,7 @@ + table inet filter { + flowtable f { + hook ingress priority filter - 1 +- devices = { lo } ++ devices = { "lo" } + counter + } + +diff --git a/tests/shell/testcases/listing/0020flowtable_0 b/tests/shell/testcases/listing/0020flowtable_0 +index 600b524..21dea2d 100755 +--- a/tests/shell/testcases/listing/0020flowtable_0 ++++ b/tests/shell/testcases/listing/0020flowtable_0 +@@ -6,11 +6,11 @@ set -e + + FLOWTABLES="flowtable f { + hook ingress priority filter +- devices = { lo } ++ devices = { \"lo\" } + } + flowtable f2 { + hook ingress priority filter +- devices = { d0 } ++ devices = { \"d0\" } + }" + + RULESET="table inet filter { +@@ -23,23 +23,23 @@ table ip filter { + EXPECTED="table inet filter { + flowtable f { + hook ingress priority filter +- devices = { lo } ++ devices = { \"lo\" } + } + }" + EXPECTED2="table ip filter { + flowtable f2 { + hook ingress priority filter +- devices = { d0 } ++ devices = { \"d0\" } + } + }" + EXPECTED3="table ip filter { + flowtable f { + hook ingress priority filter +- devices = { lo } ++ devices = { \"lo\" } + } + flowtable f2 { + hook ingress priority filter +- devices = { d0 } ++ devices = { \"d0\" } + } + }" + +diff --git a/tests/shell/testcases/listing/dumps/0020flowtable_0.nft b/tests/shell/testcases/listing/dumps/0020flowtable_0.nft +index 4a64e53..0962e73 100644 +--- a/tests/shell/testcases/listing/dumps/0020flowtable_0.nft ++++ b/tests/shell/testcases/listing/dumps/0020flowtable_0.nft +@@ -1,7 +1,7 @@ + table inet filter { + flowtable f { + hook ingress priority filter +- devices = { lo } ++ devices = { "lo" } + } + + flowtable f2 { +@@ -11,7 +11,7 @@ table inet filter { + table ip filter { + flowtable f { + hook ingress priority filter +- devices = { lo } ++ devices = { "lo" } + } + + flowtable f2 { diff --git a/0230-evaluate-validate-set-expression-type-before-accessi.patch b/0230-evaluate-validate-set-expression-type-before-accessi.patch new file mode 100644 index 0000000..077cb0f --- /dev/null +++ b/0230-evaluate-validate-set-expression-type-before-accessi.patch @@ -0,0 +1,39 @@ +From ca095ad73827371dde4fa2a0858cb0f4f2063b35 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:14:18 +0200 +Subject: [PATCH] evaluate: validate set expression type before accessing flags + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 2022e8bb5cf0e0fa81ab0a5087bd1ab6e20280ee + +commit 2022e8bb5cf0e0fa81ab0a5087bd1ab6e20280ee +Author: Pablo Neira Ayuso +Date: Wed Jul 9 00:21:49 2025 +0200 + + evaluate: validate set expression type before accessing flags + + Validate set->init is of EXPR_SET expression type before accessing + set_flags. + + Fixes: 81e36530fcac ("src: replace interval segment tree overlap and automerge") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/evaluate.c | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/src/evaluate.c b/src/evaluate.c +index 72dd4fe..73cd3c0 100644 +--- a/src/evaluate.c ++++ b/src/evaluate.c +@@ -4846,7 +4846,8 @@ static int set_evaluate(struct eval_ctx *ctx, struct set *set) + set->flags |= NFT_SET_EXPR; + + if (set_is_anonymous(set->flags)) { +- if (set_is_interval(set->init->set_flags) && ++ if (set->init->etype == EXPR_SET && ++ set_is_interval(set->init->set_flags) && + !(set->init->set_flags & NFT_SET_CONCAT) && + interval_set_eval(ctx, set, set->init) < 0) + return -1; diff --git a/0231-mnl-Call-mnl_attr_nest_end-just-once.patch b/0231-mnl-Call-mnl_attr_nest_end-just-once.patch new file mode 100644 index 0000000..8c643a7 --- /dev/null +++ b/0231-mnl-Call-mnl_attr_nest_end-just-once.patch @@ -0,0 +1,39 @@ +From 74e1c61e5d4dab13bd7125593dd4659b07c954b6 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:14:18 +0200 +Subject: [PATCH] mnl: Call mnl_attr_nest_end() just once + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 8a1de0ec9709b58a80a86a20cfba107b11c85f6e + +commit 8a1de0ec9709b58a80a86a20cfba107b11c85f6e +Author: Phil Sutter +Date: Wed Jul 16 14:26:08 2025 +0200 + + mnl: Call mnl_attr_nest_end() just once + + Calling the function after each added nested attribute is harmless but + pointless. + + Fixes: a66b5ad9540dd ("src: allow for updating devices on existing netdev chain") + Signed-off-by: Phil Sutter + +Signed-off-by: Phil Sutter +--- + src/mnl.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/mnl.c b/src/mnl.c +index d9f58de..199820d 100644 +--- a/src/mnl.c ++++ b/src/mnl.c +@@ -797,8 +797,8 @@ static void mnl_nft_chain_devs_build(struct nlmsghdr *nlh, struct cmd *cmd) + for (i = 0; i < num_devs; i++) { + cmd_add_loc(cmd, nlh, dev_array[i].location); + mnl_attr_put_strz(nlh, NFTA_DEVICE_NAME, dev_array[i].ifname); +- mnl_attr_nest_end(nlh, nest_dev); + } ++ mnl_attr_nest_end(nlh, nest_dev); + } + nft_dev_array_free(dev_array); + } diff --git a/0232-doc-nft.8-Minor-NAT-STATEMENTS-section-review.patch b/0232-doc-nft.8-Minor-NAT-STATEMENTS-section-review.patch new file mode 100644 index 0000000..2c5c796 --- /dev/null +++ b/0232-doc-nft.8-Minor-NAT-STATEMENTS-section-review.patch @@ -0,0 +1,78 @@ +From 2ee6a7b8cc1ea95b764c0e4c27fc0e129094a206 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:14:18 +0200 +Subject: [PATCH] doc: nft.8: Minor NAT STATEMENTS section review + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 9e1cbf667da2b9c30b41ff887de212b2c38b2eb7 + +commit 9e1cbf667da2b9c30b41ff887de212b2c38b2eb7 +Author: Phil Sutter +Date: Thu Jul 31 12:40:11 2025 +0200 + + doc: nft.8: Minor NAT STATEMENTS section review + + Synopsis insinuates an IP address argument is mandatory in snat/dnat + statements although specifying ports alone is perfectly fine. Adjust it + accordingly and add a paragraph briefly describing the behaviour. + + While at it, update the redirect statement description with more + relevant examples, the current one is wrong: To *only* alter the + destination port, dnat statement must be used, not redirect. + + Fixes: 6908a677ba04c ("nft.8: Enhance NAT documentation") + Signed-off-by: Phil Sutter + +Signed-off-by: Phil Sutter +--- + doc/statements.txt | 17 +++++++++++------ + 1 file changed, 11 insertions(+), 6 deletions(-) + +diff --git a/doc/statements.txt b/doc/statements.txt +index fbd01af..c4a2aff 100644 +--- a/doc/statements.txt ++++ b/doc/statements.txt +@@ -389,11 +389,12 @@ NAT STATEMENTS + ~~~~~~~~~~~~~~ + [verse] + ____ +-*snat* [[*ip* | *ip6*] [ *prefix* ] *to*] 'ADDR_SPEC' [*:*'PORT_SPEC'] ['FLAGS'] +-*dnat* [[*ip* | *ip6*] [ *prefix* ] *to*] 'ADDR_SPEC' [*:*'PORT_SPEC'] ['FLAGS'] ++*snat* [[*ip* | *ip6*] [ *prefix* ] *to*] 'TARGET_SPEC' ['FLAGS'] ++*dnat* [[*ip* | *ip6*] [ *prefix* ] *to*] 'TARGET_SPEC' ['FLAGS'] + *masquerade* [*to :*'PORT_SPEC'] ['FLAGS'] + *redirect* [*to :*'PORT_SPEC'] ['FLAGS'] + ++'TARGET_SPEC' := 'ADDR_SPEC' | ['ADDR_SPEC'] *:*'PORT_SPEC' + 'ADDR_SPEC' := 'address' | 'address' *-* 'address' + 'PORT_SPEC' := 'port' | 'port' *-* 'port' + +@@ -403,11 +404,11 @@ ____ + + The nat statements are only valid from nat chain types. + + +-The *snat* and *masquerade* statements specify that the source address of the ++The *snat* and *masquerade* statements specify that the source address/port of the + packet should be modified. While *snat* is only valid in the postrouting and + input chains, *masquerade* makes sense only in postrouting. The dnat and + redirect statements are only valid in the prerouting and output chains, they +-specify that the destination address of the packet should be modified. You can ++specify that the destination address/port of the packet should be modified. You can + use non-base chains which are called from base chains of nat chain type too. + All future packets in this connection will also be mangled, and rules should + cease being examined. +@@ -417,8 +418,12 @@ outgoing interface's IP address to translate to. It is particularly useful on + gateways with dynamic (public) IP addresses. + + The *redirect* statement is a special form of dnat which always translates the +-destination address to the local host's one. It comes in handy if one only wants +-to alter the destination port of incoming traffic on different interfaces. ++destination address to the local host's one. It comes in handy to intercept ++traffic passing a router and feeding it to a locally running daemon, e.g. when ++building a transparent proxy or application-layer gateway. ++ ++For 'TARGET_SPEC', one may specify addresses, ports, or both. If no address or ++no port is specified, the respective packet header field remains unchanged. + + When used in the inet family (available with kernel 5.2), the dnat and snat + statements require the use of the ip and ip6 keyword in case an address is diff --git a/0233-src-netlink-netlink_delinearize_table-may-return-NUL.patch b/0233-src-netlink-netlink_delinearize_table-may-return-NUL.patch new file mode 100644 index 0000000..1e37c7c --- /dev/null +++ b/0233-src-netlink-netlink_delinearize_table-may-return-NUL.patch @@ -0,0 +1,54 @@ +From cf5442be4ec760785687078c64fa41b6e5b364b2 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:14:18 +0200 +Subject: [PATCH] src: netlink: netlink_delinearize_table() may return NULL + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit a69d552a005ba467d37e225032e35d01d9491241 + +commit a69d552a005ba467d37e225032e35d01d9491241 +Author: Phil Sutter +Date: Fri Aug 8 14:21:41 2025 +0200 + + src: netlink: netlink_delinearize_table() may return NULL + + Catch the error condition in callers to avoid crashes. + + Fixes: c156232a530b3 ("src: add comment support when adding tables") + Signed-off-by: Phil Sutter + +Signed-off-by: Phil Sutter +--- + src/monitor.c | 4 ++++ + src/netlink.c | 3 ++- + 2 files changed, 6 insertions(+), 1 deletion(-) + +diff --git a/src/monitor.c b/src/monitor.c +index 2fc16d6..6414feb 100644 +--- a/src/monitor.c ++++ b/src/monitor.c +@@ -224,6 +224,10 @@ static int netlink_events_table_cb(const struct nlmsghdr *nlh, int type, + + nlt = netlink_table_alloc(nlh); + t = netlink_delinearize_table(monh->ctx, nlt); ++ if (!t) { ++ nftnl_table_free(nlt); ++ return MNL_CB_ERROR; ++ } + cmd = netlink_msg2cmd(type, nlh->nlmsg_flags); + + switch (monh->format) { +diff --git a/src/netlink.c b/src/netlink.c +index c1d3e45..19ea9f3 100644 +--- a/src/netlink.c ++++ b/src/netlink.c +@@ -783,7 +783,8 @@ static int list_table_cb(struct nftnl_table *nlt, void *arg) + struct table *table; + + table = netlink_delinearize_table(ctx, nlt); +- list_add_tail(&table->list, &ctx->list); ++ if (table) ++ list_add_tail(&table->list, &ctx->list); + + return 0; + } diff --git a/0234-tests-py-Drop-stale-entries-since-redundant-test-cas.patch b/0234-tests-py-Drop-stale-entries-since-redundant-test-cas.patch new file mode 100644 index 0000000..1a079a1 --- /dev/null +++ b/0234-tests-py-Drop-stale-entries-since-redundant-test-cas.patch @@ -0,0 +1,340 @@ +From 8851582ee320a10cc8cab72664dac1e139485298 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:14:18 +0200 +Subject: [PATCH] tests: py: Drop stale entries since redundant test case + removal + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 7db422da28349edcdeb3c96a410b242856ca74ac + +commit 7db422da28349edcdeb3c96a410b242856ca74ac +Author: Phil Sutter +Date: Wed Aug 13 14:14:45 2025 +0200 + + tests: py: Drop stale entries since redundant test case removal + + Fixed commit left stale JSON equivalents and payload records in place, + drop them. + + Fixes: ec1ea13314fa5 ("tests: remove redundant test cases") + Signed-off-by: Phil Sutter + Reviewed-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + tests/py/any/meta.t.json | 38 ---------- + tests/py/ip6/dst.t.json | 23 ------ + tests/py/ip6/dst.t.payload.inet | 10 --- + tests/py/ip6/dst.t.payload.ip6 | 8 --- + tests/py/ip6/frag.t.json | 121 -------------------------------- + tests/py/ip6/ip6.t.json | 40 ----------- + 6 files changed, 240 deletions(-) + +diff --git a/tests/py/any/meta.t.json b/tests/py/any/meta.t.json +index d50272d..b315015 100644 +--- a/tests/py/any/meta.t.json ++++ b/tests/py/any/meta.t.json +@@ -2294,44 +2294,6 @@ + } + ] + +-# meta cgroup {1048577-1048578} +-[ +- { +- "match": { +- "left": { +- "meta": { "key": "cgroup" } +- }, +- "op": "==", +- "right": { +- "set": [ +- { +- "range": [ 1048577, 1048578 ] +- } +- ] +- } +- } +- } +-] +- +-# meta cgroup != { 1048577-1048578} +-[ +- { +- "match": { +- "left": { +- "meta": { "key": "cgroup" } +- }, +- "op": "!=", +- "right": { +- "set": [ +- { +- "range": [ 1048577, 1048578 ] +- } +- ] +- } +- } +- } +-] +- + # meta iif . meta oif { "lo" . "lo" } + [ + { +diff --git a/tests/py/ip6/dst.t.json b/tests/py/ip6/dst.t.json +index e947a76..7ba4572 100644 +--- a/tests/py/ip6/dst.t.json ++++ b/tests/py/ip6/dst.t.json +@@ -290,26 +290,3 @@ + } + } + ] +- +-# dst hdrlength != { 33, 55, 67, 88} +-[ +- { +- "match": { +- "left": { +- "exthdr": { +- "field": "hdrlength", +- "name": "dst" +- } +- }, +- "op": "!=", +- "right": { +- "set": [ +- 33, +- 55, +- 67, +- 88 +- ] +- } +- } +- } +-] +diff --git a/tests/py/ip6/dst.t.payload.inet b/tests/py/ip6/dst.t.payload.inet +index 90d6bda..b2a79ed 100644 +--- a/tests/py/ip6/dst.t.payload.inet ++++ b/tests/py/ip6/dst.t.payload.inet +@@ -119,13 +119,3 @@ ip6 test-ip6 input + [ cmp eq reg 1 0x0000000a ] + [ exthdr load ipv6 1b @ 60 + 1 => reg 1 ] + [ lookup reg 1 set __set%d ] +- +-# dst hdrlength != { 33, 55, 67, 88} +-__set%d test-ip6 3 +-__set%d test-ip6 0 +- element 00000021 : 0 [end] element 00000037 : 0 [end] element 00000043 : 0 [end] element 00000058 : 0 [end] +-ip6 test-ip6 input +- [ meta load nfproto => reg 1 ] +- [ cmp eq reg 1 0x0000000a ] +- [ exthdr load ipv6 1b @ 60 + 1 => reg 1 ] +- [ lookup reg 1 set __set%d 0x1 ] +diff --git a/tests/py/ip6/dst.t.payload.ip6 b/tests/py/ip6/dst.t.payload.ip6 +index 941140d..5058b46 100644 +--- a/tests/py/ip6/dst.t.payload.ip6 ++++ b/tests/py/ip6/dst.t.payload.ip6 +@@ -89,11 +89,3 @@ __set%d test-ip6 0 + ip6 test-ip6 input + [ exthdr load ipv6 1b @ 60 + 1 => reg 1 ] + [ lookup reg 1 set __set%d ] +- +-# dst hdrlength != { 33, 55, 67, 88} +-__set%d test-ip6 3 +-__set%d test-ip6 0 +- element 00000021 : 0 [end] element 00000037 : 0 [end] element 00000043 : 0 [end] element 00000058 : 0 [end] +-ip6 test-ip6 input +- [ exthdr load ipv6 1b @ 60 + 1 => reg 1 ] +- [ lookup reg 1 set __set%d 0x1 ] +diff --git a/tests/py/ip6/frag.t.json b/tests/py/ip6/frag.t.json +index b8c06df..6953e87 100644 +--- a/tests/py/ip6/frag.t.json ++++ b/tests/py/ip6/frag.t.json +@@ -230,46 +230,6 @@ + } + ] + +-# frag reserved { 33-55} +-[ +- { +- "match": { +- "left": { +- "exthdr": { +- "field": "reserved", +- "name": "frag" +- } +- }, +- "op": "==", +- "right": { +- "set": [ +- { "range": [ 33, 55 ] } +- ] +- } +- } +- } +-] +- +-# frag reserved != { 33-55} +-[ +- { +- "match": { +- "left": { +- "exthdr": { +- "field": "reserved", +- "name": "frag" +- } +- }, +- "op": "!=", +- "right": { +- "set": [ +- { "range": [ 33, 55 ] } +- ] +- } +- } +- } +-] +- + # frag frag-off 22 + [ + { +@@ -384,46 +344,6 @@ + } + ] + +-# frag frag-off { 33-55} +-[ +- { +- "match": { +- "left": { +- "exthdr": { +- "field": "frag-off", +- "name": "frag" +- } +- }, +- "op": "==", +- "right": { +- "set": [ +- { "range": [ 33, 55 ] } +- ] +- } +- } +- } +-] +- +-# frag frag-off != { 33-55} +-[ +- { +- "match": { +- "left": { +- "exthdr": { +- "field": "frag-off", +- "name": "frag" +- } +- }, +- "op": "!=", +- "right": { +- "set": [ +- { "range": [ 33, 55 ] } +- ] +- } +- } +- } +-] +- + # frag reserved2 1 + [ + { +@@ -601,44 +521,3 @@ + } + } + ] +- +-# frag id { 33-55} +-[ +- { +- "match": { +- "left": { +- "exthdr": { +- "field": "id", +- "name": "frag" +- } +- }, +- "op": "==", +- "right": { +- "set": [ +- { "range": [ 33, 55 ] } +- ] +- } +- } +- } +-] +- +-# frag id != { 33-55} +-[ +- { +- "match": { +- "left": { +- "exthdr": { +- "field": "id", +- "name": "frag" +- } +- }, +- "op": "!=", +- "right": { +- "set": [ +- { "range": [ 33, 55 ] } +- ] +- } +- } +- } +-] +- +diff --git a/tests/py/ip6/ip6.t.json b/tests/py/ip6/ip6.t.json +index 49e5a2d..72d91cc 100644 +--- a/tests/py/ip6/ip6.t.json ++++ b/tests/py/ip6/ip6.t.json +@@ -571,46 +571,6 @@ + } + ] + +-# ip6 nexthdr { 33-44} +-[ +- { +- "match": { +- "left": { +- "payload": { +- "field": "nexthdr", +- "protocol": "ip6" +- } +- }, +- "op": "==", +- "right": { +- "set": [ +- { "range": [ 33, 44 ] } +- ] +- } +- } +- } +-] +- +-# ip6 nexthdr != { 33-44} +-[ +- { +- "match": { +- "left": { +- "payload": { +- "field": "nexthdr", +- "protocol": "ip6" +- } +- }, +- "op": "!=", +- "right": { +- "set": [ +- { "range": [ 33, 44 ] } +- ] +- } +- } +- } +-] +- + # ip6 nexthdr 33-44 + [ + { diff --git a/0235-tests-py-Drop-duplicate-test-from-inet-geneve.t.patch b/0235-tests-py-Drop-duplicate-test-from-inet-geneve.t.patch new file mode 100644 index 0000000..0d98956 --- /dev/null +++ b/0235-tests-py-Drop-duplicate-test-from-inet-geneve.t.patch @@ -0,0 +1,79 @@ +From b28144bf4ec73a21ef8f2cd90979cf6af6902abb Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:14:18 +0200 +Subject: [PATCH] tests: py: Drop duplicate test from inet/geneve.t + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit a3b279794c2e26f9921445163e9bde0df5bda651 + +commit a3b279794c2e26f9921445163e9bde0df5bda651 +Author: Phil Sutter +Date: Wed Aug 13 14:19:31 2025 +0200 + + tests: py: Drop duplicate test from inet/geneve.t + + The test was duplicate since day 1. The duplicate JSON equivalent was + added later (semi-automated), remove it as well. + + Fixes: 2b9143bc7ab81 ("tests: py: add geneve tests") + Signed-off-by: Phil Sutter + Reviewed-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + tests/py/inet/geneve.t | 1 - + tests/py/inet/geneve.t.json | 29 ----------------------------- + 2 files changed, 30 deletions(-) + +diff --git a/tests/py/inet/geneve.t b/tests/py/inet/geneve.t +index 101f6df..ad46272 100644 +--- a/tests/py/inet/geneve.t ++++ b/tests/py/inet/geneve.t +@@ -17,7 +17,6 @@ udp dport 6081 geneve icmp type echo-reply;ok + udp dport 6081 geneve ether saddr 62:87:4d:d6:19:05;ok + udp dport 6081 geneve vlan id 10;ok + udp dport 6081 geneve ip dscp 0x02;ok +-udp dport 6081 geneve ip dscp 0x02;ok + udp dport 6081 geneve ip saddr . geneve ip daddr { 1.2.3.4 . 4.3.2.1 };ok + + udp dport 6081 geneve ip saddr set 1.2.3.4;fail +diff --git a/tests/py/inet/geneve.t.json b/tests/py/inet/geneve.t.json +index a299fcd..1589918 100644 +--- a/tests/py/inet/geneve.t.json ++++ b/tests/py/inet/geneve.t.json +@@ -264,35 +264,6 @@ + } + ] + +-# udp dport 6081 geneve ip dscp 0x02 +-[ +- { +- "match": { +- "left": { +- "payload": { +- "field": "dport", +- "protocol": "udp" +- } +- }, +- "op": "==", +- "right": 6081 +- } +- }, +- { +- "match": { +- "left": { +- "payload": { +- "field": "dscp", +- "protocol": "ip", +- "tunnel": "geneve" +- } +- }, +- "op": "==", +- "right": 2 +- } +- } +-] +- + # udp dport 6081 geneve ip saddr . geneve ip daddr { 1.2.3.4 . 4.3.2.1 } + [ + { diff --git a/0236-tests-py-Drop-duplicate-test-from-inet-gre.t.patch b/0236-tests-py-Drop-duplicate-test-from-inet-gre.t.patch new file mode 100644 index 0000000..628c455 --- /dev/null +++ b/0236-tests-py-Drop-duplicate-test-from-inet-gre.t.patch @@ -0,0 +1,67 @@ +From 76891e5379ac0f70d4d775be2690da46ed3685f6 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:14:18 +0200 +Subject: [PATCH] tests: py: Drop duplicate test from inet/gre.t + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 7a566cb4e7b98c5f6509d9cb25d0281d5198da28 + +commit 7a566cb4e7b98c5f6509d9cb25d0281d5198da28 +Author: Phil Sutter +Date: Wed Aug 13 14:22:07 2025 +0200 + + tests: py: Drop duplicate test from inet/gre.t + + The test was duplicate since day 1. The duplicate JSON equivalent was + added later (semi-automated), remove it as well. + + Fixes: c04ef8d104ec6 ("tests: py: add gre tests") + Signed-off-by: Phil Sutter + Reviewed-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + tests/py/inet/gre.t | 1 - + tests/py/inet/gre.t.json | 17 ----------------- + 2 files changed, 18 deletions(-) + +diff --git a/tests/py/inet/gre.t b/tests/py/inet/gre.t +index a3e046a..a21e67e 100644 +--- a/tests/py/inet/gre.t ++++ b/tests/py/inet/gre.t +@@ -16,7 +16,6 @@ gre icmp type echo-reply;ok + gre ether saddr 62:87:4d:d6:19:05;fail + gre vlan id 10;fail + gre ip dscp 0x02;ok +-gre ip dscp 0x02;ok + gre ip saddr . gre ip daddr { 1.2.3.4 . 4.3.2.1 };ok + + gre ip saddr set 1.2.3.4;fail +diff --git a/tests/py/inet/gre.t.json b/tests/py/inet/gre.t.json +index c443176..a354e6b 100644 +--- a/tests/py/inet/gre.t.json ++++ b/tests/py/inet/gre.t.json +@@ -121,23 +121,6 @@ + } + ] + +-# gre ip dscp 0x02 +-[ +- { +- "match": { +- "left": { +- "payload": { +- "field": "dscp", +- "protocol": "ip", +- "tunnel": "gre" +- } +- }, +- "op": "==", +- "right": 2 +- } +- } +-] +- + # gre ip saddr . gre ip daddr { 1.2.3.4 . 4.3.2.1 } + [ + { diff --git a/0237-tests-py-Drop-duplicate-test-from-inet-gretap.t.patch b/0237-tests-py-Drop-duplicate-test-from-inet-gretap.t.patch new file mode 100644 index 0000000..c089bf7 --- /dev/null +++ b/0237-tests-py-Drop-duplicate-test-from-inet-gretap.t.patch @@ -0,0 +1,67 @@ +From 519cf2518d490008198fb584ecf1fc55b47b6802 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:14:18 +0200 +Subject: [PATCH] tests: py: Drop duplicate test from inet/gretap.t + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit e159957099746a294ce767c16665d77b5ef2d464 + +commit e159957099746a294ce767c16665d77b5ef2d464 +Author: Phil Sutter +Date: Wed Aug 13 14:23:30 2025 +0200 + + tests: py: Drop duplicate test from inet/gretap.t + + The test was duplicate since day 1. The duplicate JSON equivalent was + added later (semi-automated), remove it as well. + + Fixes: 39a68d9ffd25c ("tests: py: add gretap tests") + Signed-off-by: Phil Sutter + Reviewed-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + tests/py/inet/gretap.t | 1 - + tests/py/inet/gretap.t.json | 17 ----------------- + 2 files changed, 18 deletions(-) + +diff --git a/tests/py/inet/gretap.t b/tests/py/inet/gretap.t +index cd7ee21..f88896f 100644 +--- a/tests/py/inet/gretap.t ++++ b/tests/py/inet/gretap.t +@@ -15,7 +15,6 @@ gretap icmp type echo-reply;ok + gretap ether saddr 62:87:4d:d6:19:05;ok + gretap vlan id 10;ok + gretap ip dscp 0x02;ok +-gretap ip dscp 0x02;ok + gretap ip saddr . gretap ip daddr { 1.2.3.4 . 4.3.2.1 };ok + + gretap ip saddr set 1.2.3.4;fail +diff --git a/tests/py/inet/gretap.t.json b/tests/py/inet/gretap.t.json +index 36fa978..6c16a08 100644 +--- a/tests/py/inet/gretap.t.json ++++ b/tests/py/inet/gretap.t.json +@@ -139,23 +139,6 @@ + } + ] + +-# gretap ip dscp 0x02 +-[ +- { +- "match": { +- "left": { +- "payload": { +- "field": "dscp", +- "protocol": "ip", +- "tunnel": "gretap" +- } +- }, +- "op": "==", +- "right": 2 +- } +- } +-] +- + # gretap ip saddr . gretap ip daddr { 1.2.3.4 . 4.3.2.1 } + [ + { diff --git a/0238-tests-py-Drop-stale-entry-from-inet-tcp.t.json.patch b/0238-tests-py-Drop-stale-entry-from-inet-tcp.t.json.patch new file mode 100644 index 0000000..75285b0 --- /dev/null +++ b/0238-tests-py-Drop-stale-entry-from-inet-tcp.t.json.patch @@ -0,0 +1,69 @@ +From 71414c3a97deb69dbd107f0173edaf5b74aa3a86 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:14:18 +0200 +Subject: [PATCH] tests: py: Drop stale entry from inet/tcp.t.json + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit f8fe3d56194f06bea9cd82d1d707064b202f5202 + +commit f8fe3d56194f06bea9cd82d1d707064b202f5202 +Author: Phil Sutter +Date: Wed Aug 13 14:32:11 2025 +0200 + + tests: py: Drop stale entry from inet/tcp.t.json + + The test was changed but JSON equivalents not updated. Commit + c0b685951fabb ("json: fix parse of flagcmp expression") then added an + equivalent matching the changed test, so just drop the old one. + + Fixes: c3d57114f119b ("parser_bison: add shortcut syntax for matching flags without binary operations") + Signed-off-by: Phil Sutter + Reviewed-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + tests/py/inet/tcp.t.json | 31 ------------------------------- + 1 file changed, 31 deletions(-) + +diff --git a/tests/py/inet/tcp.t.json b/tests/py/inet/tcp.t.json +index bd589cf..95a9e48 100644 +--- a/tests/py/inet/tcp.t.json ++++ b/tests/py/inet/tcp.t.json +@@ -910,37 +910,6 @@ + } + ] + +-# tcp flags & (syn|fin) == (syn|fin) +-[ +- { +- "match": { +- "left": { +- "&": [ +- { +- "payload": { +- "field": "flags", +- "protocol": "tcp" +- } +- }, +- { +- "|": [ +- "syn", +- "fin" +- ] +- } +- ] +- }, +- "op": "==", +- "right": { +- "|": [ +- "syn", +- "fin" +- ] +- } +- } +- } +-] +- + # tcp flags & (fin | syn | rst | psh | ack | urg | ecn | cwr) == fin | syn | rst | psh | ack | urg | ecn | cwr + [ + { diff --git a/0239-tests-py-Drop-duplicate-test-from-inet-vxlan.t.patch b/0239-tests-py-Drop-duplicate-test-from-inet-vxlan.t.patch new file mode 100644 index 0000000..66ed607 --- /dev/null +++ b/0239-tests-py-Drop-duplicate-test-from-inet-vxlan.t.patch @@ -0,0 +1,79 @@ +From e6fb7ad53edde922c73adda9a2f16cb2be474ed7 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:14:18 +0200 +Subject: [PATCH] tests: py: Drop duplicate test from inet/vxlan.t + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit bc66b02a6bb86a71d0a42ea7851f9aeea20edf1c + +commit bc66b02a6bb86a71d0a42ea7851f9aeea20edf1c +Author: Phil Sutter +Date: Wed Aug 13 14:38:22 2025 +0200 + + tests: py: Drop duplicate test from inet/vxlan.t + + The test was duplicate since day 1. The duplicate JSON equivalent was + added later (semi-automated), remove it as well. + + Fixes: df81baa4c2bef ("tests: py: add vxlan tests") + Signed-off-by: Phil Sutter + Reviewed-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + tests/py/inet/vxlan.t | 1 - + tests/py/inet/vxlan.t.json | 29 ----------------------------- + 2 files changed, 30 deletions(-) + +diff --git a/tests/py/inet/vxlan.t b/tests/py/inet/vxlan.t +index 10cdb7a..b6db0fe 100644 +--- a/tests/py/inet/vxlan.t ++++ b/tests/py/inet/vxlan.t +@@ -17,7 +17,6 @@ udp dport 4789 vxlan icmp type echo-reply;ok + udp dport 4789 vxlan ether saddr 62:87:4d:d6:19:05;ok + udp dport 4789 vxlan vlan id 10;ok + udp dport 4789 vxlan ip dscp 0x02;ok +-udp dport 4789 vxlan ip dscp 0x02;ok + udp dport 4789 vxlan ip saddr . vxlan ip daddr { 1.2.3.4 . 4.3.2.1 };ok + + udp dport 4789 vxlan ip saddr set 1.2.3.4;fail +diff --git a/tests/py/inet/vxlan.t.json b/tests/py/inet/vxlan.t.json +index 91b3d29..3c147cb 100644 +--- a/tests/py/inet/vxlan.t.json ++++ b/tests/py/inet/vxlan.t.json +@@ -264,35 +264,6 @@ + } + ] + +-# udp dport 4789 vxlan ip dscp 0x02 +-[ +- { +- "match": { +- "left": { +- "payload": { +- "field": "dport", +- "protocol": "udp" +- } +- }, +- "op": "==", +- "right": 4789 +- } +- }, +- { +- "match": { +- "left": { +- "payload": { +- "field": "dscp", +- "protocol": "ip", +- "tunnel": "vxlan" +- } +- }, +- "op": "==", +- "right": 2 +- } +- } +-] +- + # udp dport 4789 vxlan ip saddr . vxlan ip daddr { 1.2.3.4 . 4.3.2.1 } + [ + { diff --git a/0240-tests-py-Drop-stale-entry-from-ip-snat.t.json.patch b/0240-tests-py-Drop-stale-entry-from-ip-snat.t.json.patch new file mode 100644 index 0000000..9cbe7ea --- /dev/null +++ b/0240-tests-py-Drop-stale-entry-from-ip-snat.t.json.patch @@ -0,0 +1,70 @@ +From 0e06c0f34882d70f4cb8242dfa3653f5f325378a Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:14:18 +0200 +Subject: [PATCH] tests: py: Drop stale entry from ip/snat.t.json + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 71d90776fc56b14250f2306c4e19b50c23bd3ae3 + +commit 71d90776fc56b14250f2306c4e19b50c23bd3ae3 +Author: Phil Sutter +Date: Wed Aug 13 15:03:29 2025 +0200 + + tests: py: Drop stale entry from ip/snat.t.json + + The test syntax was changed, but the respective JSON equivalent remained + in place. + + Fixes: 9b169bfc650eb ("src: remove STMT_NAT_F_INTERVAL flags and interval keyword") + Signed-off-by: Phil Sutter + Reviewed-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + tests/py/ip/snat.t.json | 33 --------------------------------- + 1 file changed, 33 deletions(-) + +diff --git a/tests/py/ip/snat.t.json b/tests/py/ip/snat.t.json +index 967560e..60bd093 100644 +--- a/tests/py/ip/snat.t.json ++++ b/tests/py/ip/snat.t.json +@@ -283,39 +283,6 @@ + } + ] + +-# snat ip interval to ip saddr map { 10.141.11.4 : 192.168.2.2-192.168.2.4 } +-[ +- { +- "snat": { +- "addr": { +- "map": { +- "data": { +- "set": [ +- [ +- "10.141.11.4", +- { +- "range": [ +- "192.168.2.2", +- "192.168.2.4" +- ] +- } +- ] +- ] +- }, +- "key": { +- "payload": { +- "field": "saddr", +- "protocol": "ip" +- } +- } +- } +- }, +- "family": "ip", +- "type_flags": "interval" +- } +- } +-] +- + # snat ip prefix to ip saddr map { 10.141.11.0/24 : 192.168.2.0/24 } + [ + { diff --git a/0241-tests-py-Drop-stale-entries-from-ip6-ct-meta-.t.json.patch b/0241-tests-py-Drop-stale-entries-from-ip6-ct-meta-.t.json.patch new file mode 100644 index 0000000..17112ed --- /dev/null +++ b/0241-tests-py-Drop-stale-entries-from-ip6-ct-meta-.t.json.patch @@ -0,0 +1,268 @@ +From aeb8c8c64be9e863f0e498c6b82e00528bbddfa7 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:14:18 +0200 +Subject: [PATCH] tests: py: Drop stale entries from ip6/{ct,meta}.t.json + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 3be0bbe9a5ba21a3f6e3baf051c3aaadc393c587 + +commit 3be0bbe9a5ba21a3f6e3baf051c3aaadc393c587 +Author: Phil Sutter +Date: Wed Aug 13 15:50:54 2025 +0200 + + tests: py: Drop stale entries from ip6/{ct,meta}.t.json + + Looks like these were added by accident, fixed commit did not add these + test cases. + + Fixes: 8221d86e616bd ("tests: py: add test-cases for ct and packet mark payload expressions") + Signed-off-by: Phil Sutter + Reviewed-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + tests/py/ip6/ct.t.json | 164 --------------------------------------- + tests/py/ip6/meta.t.json | 58 -------------- + 2 files changed, 222 deletions(-) + +diff --git a/tests/py/ip6/ct.t.json b/tests/py/ip6/ct.t.json +index 7d8c88b..a7511b9 100644 +--- a/tests/py/ip6/ct.t.json ++++ b/tests/py/ip6/ct.t.json +@@ -1,167 +1,3 @@ +-# ct mark set ip6 dscp lshift 2 or 0x10 +-[ +- { +- "mangle": { +- "key": { +- "ct": { +- "key": "mark" +- } +- }, +- "value": { +- "|": [ +- { +- "<<": [ +- { +- "payload": { +- "field": "dscp", +- "protocol": "ip6" +- } +- }, +- 2 +- ] +- }, +- 16 +- ] +- } +- } +- } +-] +- +-# ct mark set ip6 dscp lshift 26 or 0x10 +-[ +- { +- "mangle": { +- "key": { +- "ct": { +- "key": "mark" +- } +- }, +- "value": { +- "|": [ +- { +- "<<": [ +- { +- "payload": { +- "field": "dscp", +- "protocol": "ip6" +- } +- }, +- 26 +- ] +- }, +- 16 +- ] +- } +- } +- } +-] +- +-# ct mark set ip6 dscp << 2 | 0x10 +-[ +- { +- "mangle": { +- "key": { +- "ct": { +- "key": "mark" +- } +- }, +- "value": { +- "|": [ +- { +- "<<": [ +- { +- "payload": { +- "field": "dscp", +- "protocol": "ip6" +- } +- }, +- 2 +- ] +- }, +- 16 +- ] +- } +- } +- } +-] +- +-# ct mark set ip6 dscp << 26 | 0x10 +-[ +- { +- "mangle": { +- "key": { +- "ct": { +- "key": "mark" +- } +- }, +- "value": { +- "|": [ +- { +- "<<": [ +- { +- "payload": { +- "field": "dscp", +- "protocol": "ip6" +- } +- }, +- 26 +- ] +- }, +- 16 +- ] +- } +- } +- } +-] +- +-# ct mark set ip6 dscp | 0x04 +-[ +- { +- "mangle": { +- "key": { +- "ct": { +- "key": "mark" +- } +- }, +- "value": { +- "|": [ +- { +- "payload": { +- "field": "dscp", +- "protocol": "ip6" +- } +- }, +- 4 +- ] +- } +- } +- } +-] +- +-# ct mark set ip6 dscp | 0xff000000 +-[ +- { +- "mangle": { +- "key": { +- "ct": { +- "key": "mark" +- } +- }, +- "value": { +- "|": [ +- { +- "payload": { +- "field": "dscp", +- "protocol": "ip6" +- } +- }, +- 4278190080 +- ] +- } +- } +- } +-] +- + # ct mark set ip6 dscp << 2 | 0x10 + [ + { +diff --git a/tests/py/ip6/meta.t.json b/tests/py/ip6/meta.t.json +index 1a2394d..87251f0 100644 +--- a/tests/py/ip6/meta.t.json ++++ b/tests/py/ip6/meta.t.json +@@ -195,64 +195,6 @@ + } + ] + +-# meta mark set ip6 dscp lshift 2 or 0x10 +-[ +- { +- "mangle": { +- "key": { +- "meta": { +- "key": "mark" +- } +- }, +- "value": { +- "|": [ +- { +- "<<": [ +- { +- "payload": { +- "field": "dscp", +- "protocol": "ip6" +- } +- }, +- 2 +- ] +- }, +- 16 +- ] +- } +- } +- } +-] +- +-# meta mark set ip6 dscp lshift 26 or 0x10 +-[ +- { +- "mangle": { +- "key": { +- "meta": { +- "key": "mark" +- } +- }, +- "value": { +- "|": [ +- { +- "<<": [ +- { +- "payload": { +- "field": "dscp", +- "protocol": "ip6" +- } +- }, +- 26 +- ] +- }, +- 16 +- ] +- } +- } +- } +-] +- + # meta mark set ip6 dscp << 2 | 0x10 + [ + { diff --git a/0242-tests-py-Drop-stale-entry-from-ip-snat.t.payload.patch b/0242-tests-py-Drop-stale-entry-from-ip-snat.t.payload.patch new file mode 100644 index 0000000..e79858a --- /dev/null +++ b/0242-tests-py-Drop-stale-entry-from-ip-snat.t.payload.patch @@ -0,0 +1,48 @@ +From 27b765e07c47811952f1480b3d60a421dcc79254 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:14:18 +0200 +Subject: [PATCH] tests: py: Drop stale entry from ip/snat.t.payload + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit c3c60ac84a4f64a98f9c2c22d9032d177ae6a212 + +commit c3c60ac84a4f64a98f9c2c22d9032d177ae6a212 +Author: Phil Sutter +Date: Wed Aug 13 16:06:46 2025 +0200 + + tests: py: Drop stale entry from ip/snat.t.payload + + This payload actually belongs to ip/dnat.t.payload, fixed commit added + it to the wrong file. + + Fixes: 8f3048954d40d ("evaluate: postpone transport protocol match check after nat expression evaluation") + Signed-off-by: Phil Sutter + Reviewed-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + tests/py/ip/snat.t.payload | 14 -------------- + 1 file changed, 14 deletions(-) + +diff --git a/tests/py/ip/snat.t.payload b/tests/py/ip/snat.t.payload +index 71a5e2f..9a59b71 100644 +--- a/tests/py/ip/snat.t.payload ++++ b/tests/py/ip/snat.t.payload +@@ -138,17 +138,3 @@ ip + [ payload load 2b @ transport header + 2 => reg 9 ] + [ lookup reg 1 set __map%d dreg 1 ] + [ nat snat ip addr_min reg 1 proto_min reg 9 ] +- +-# ip daddr 192.168.0.1 dnat to tcp dport map { 443 : 10.141.10.4 . 8443, 80 : 10.141.10.4 . 8080 } +-__map%d x b size 2 +-__map%d x 0 +- element 0000bb01 : 040a8d0a 0000fb20 0 [end] element 00005000 : 040a8d0a 0000901f 0 [end] +-ip +- [ payload load 4b @ network header + 16 => reg 1 ] +- [ cmp eq reg 1 0x0100a8c0 ] +- [ meta load l4proto => reg 1 ] +- [ cmp eq reg 1 0x00000006 ] +- [ payload load 2b @ transport header + 2 => reg 1 ] +- [ lookup reg 1 set __map%d dreg 1 ] +- [ nat dnat ip addr_min reg 1 proto_min reg 9 ] +- diff --git a/0243-tests-py-Fix-tests-added-for-icmpv6-taddr-support.patch b/0243-tests-py-Fix-tests-added-for-icmpv6-taddr-support.patch new file mode 100644 index 0000000..dc720b1 --- /dev/null +++ b/0243-tests-py-Fix-tests-added-for-icmpv6-taddr-support.patch @@ -0,0 +1,155 @@ +From 2677bdc9830c7b1930d1995759647b982a9a7986 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:14:19 +0200 +Subject: [PATCH] tests: py: Fix tests added for 'icmpv6 taddr' support + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit ef8b8ef36f80a2bfcc9df35cccb61ab490a8d6e7 + +commit ef8b8ef36f80a2bfcc9df35cccb61ab490a8d6e7 +Author: Phil Sutter +Date: Wed Aug 13 16:14:08 2025 +0200 + + tests: py: Fix tests added for 'icmpv6 taddr' support + + There was a duplicate test, also stored JSON equivalents should match + input as much as possible. The expected deviation in output (just like + with standard syntax) is stored in the .json.output file instead. + + Fixes: 2e86f45d0260a ("icmpv6: Allow matching target address in NS/NA, redirect and MLD") + Signed-off-by: Phil Sutter + Reviewed-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + tests/py/ip6/icmpv6.t | 2 -- + tests/py/ip6/icmpv6.t.json | 58 ------------------------------- + tests/py/ip6/icmpv6.t.json.output | 36 +++++++++++++++++++ + 3 files changed, 36 insertions(+), 60 deletions(-) + +diff --git a/tests/py/ip6/icmpv6.t b/tests/py/ip6/icmpv6.t +index 7632bfd..5108b42 100644 +--- a/tests/py/ip6/icmpv6.t ++++ b/tests/py/ip6/icmpv6.t +@@ -91,8 +91,6 @@ icmpv6 type nd-neighbor-solicit icmpv6 taddr 2001:db8::133;ok + icmpv6 type nd-neighbor-advert icmpv6 taddr 2001:db8::133;ok + icmpv6 taddr 2001:db8::133;ok;icmpv6 type { mld-listener-query, mld-listener-report, mld-listener-done, nd-neighbor-solicit, nd-neighbor-advert, nd-redirect} icmpv6 taddr 2001:db8::133 + +-icmpv6 taddr 2001:db8::133;ok;icmpv6 type { mld-listener-query, mld-listener-report, mld-listener-done, nd-neighbor-solicit, nd-neighbor-advert, nd-redirect} icmpv6 taddr 2001:db8::133 +- + icmpv6 type { mld-listener-query, mld-listener-report, mld-listener-done, nd-neighbor-solicit, nd-neighbor-advert, nd-redirect} icmpv6 taddr 2001:db8::133;ok + icmpv6 type { nd-neighbor-solicit, nd-neighbor-advert } icmpv6 taddr 2001:db8::133;ok + icmpv6 daddr 2001:db8::133;ok +diff --git a/tests/py/ip6/icmpv6.t.json b/tests/py/ip6/icmpv6.t.json +index 9df886d..5c36aab 100644 +--- a/tests/py/ip6/icmpv6.t.json ++++ b/tests/py/ip6/icmpv6.t.json +@@ -1250,64 +1250,6 @@ + + # icmpv6 taddr 2001:db8::133 + [ +- { +- "match": { +- "left": { +- "payload": { +- "field": "type", +- "protocol": "icmpv6" +- } +- }, +- "op": "==", +- "right": { +- "set": [ +- "mld-listener-query", +- "mld-listener-report", +- "mld-listener-done", +- "nd-neighbor-solicit", +- "nd-neighbor-advert", +- "nd-redirect" +- ] +- } +- } +- }, +- { +- "match": { +- "left": { +- "payload": { +- "field": "taddr", +- "protocol": "icmpv6" +- } +- }, +- "op": "==", +- "right": "2001:db8::133" +- } +- } +-] +- +-# icmpv6 taddr 2001:db8::133 +-[ +- { +- "match": { +- "left": { +- "payload": { +- "field": "type", +- "protocol": "icmpv6" +- } +- }, +- "op": "==", +- "right": { +- "set": [ +- "mld-listener-query", +- "mld-listener-report", +- "mld-listener-done", +- "nd-neighbor-solicit", +- "nd-neighbor-advert", +- "nd-redirect" +- ] +- } +- } +- }, + { + "match": { + "left": { +diff --git a/tests/py/ip6/icmpv6.t.json.output b/tests/py/ip6/icmpv6.t.json.output +index f29b346..8704eaf 100644 +--- a/tests/py/ip6/icmpv6.t.json.output ++++ b/tests/py/ip6/icmpv6.t.json.output +@@ -758,3 +758,39 @@ + } + ] + ++# icmpv6 taddr 2001:db8::133 ++[ ++ { ++ "match": { ++ "left": { ++ "payload": { ++ "field": "type", ++ "protocol": "icmpv6" ++ } ++ }, ++ "op": "==", ++ "right": { ++ "set": [ ++ "mld-listener-query", ++ "mld-listener-report", ++ "mld-listener-done", ++ "nd-neighbor-solicit", ++ "nd-neighbor-advert", ++ "nd-redirect" ++ ] ++ } ++ } ++ }, ++ { ++ "match": { ++ "left": { ++ "payload": { ++ "field": "taddr", ++ "protocol": "icmpv6" ++ } ++ }, ++ "op": "==", ++ "right": "2001:db8::133" ++ } ++ } ++] diff --git a/0244-json-Do-not-reduce-single-item-arrays-on-output.patch b/0244-json-Do-not-reduce-single-item-arrays-on-output.patch new file mode 100644 index 0000000..05c01d2 --- /dev/null +++ b/0244-json-Do-not-reduce-single-item-arrays-on-output.patch @@ -0,0 +1,882 @@ +From c466f9a9e88eeaa0cf2968a56f1ac63fee24369a Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:14:19 +0200 +Subject: [PATCH] json: Do not reduce single-item arrays on output + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 5e492307c2c93b7eb654691ca87ddc7ad86640d8 +Conflicts: Dropped changes to non-existent .json-nft dumps + +commit 5e492307c2c93b7eb654691ca87ddc7ad86640d8 +Author: Phil Sutter +Date: Tue Aug 12 17:31:47 2025 +0200 + + json: Do not reduce single-item arrays on output + + This is a partial revert of commit a740f2036ad0d ("json: Introduce + json_add_array_new()"), keeping the function but eliminating its primary + task which is to replace arrays of size 1 by their only item. While + support for this on input is convenient for users, it means extra casing + in JSON output parsers to cover for it. The minor reduction in output + size does not justify that. + + Fixes: a740f2036ad0d ("json: Introduce json_add_array_new()") + Closes: https://bugzilla.netfilter.org/show_bug.cgi?id=1806 + Signed-off-by: Phil Sutter + Reviewed-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/json.c | 8 +- + tests/py/any/log.t.json.output | 15 +++- + tests/py/any/queue.t.json.output | 117 ++++++++++++++++++++++++ + tests/py/inet/fib.t.json | 8 +- + tests/py/inet/fib.t.json.output | 87 +++++++++++++++++- + tests/py/inet/snat.t.json.output | 22 +++++ + tests/py/inet/synproxy.t.json | 8 +- + tests/py/inet/synproxy.t.json.output | 8 +- + tests/py/ip/masquerade.t.json.output | 46 ++++++++++ + tests/py/ip/redirect.t.json.output | 118 +++++++++++++++++++++++++ + tests/py/ip/snat.t.json.output | 8 +- + tests/py/ip6/masquerade.t.json.output | 46 ++++++++++ + tests/py/ip6/redirect.t.json.output | 70 +++++++++++++++ + tests/shell/testcases/json/single_flag | 48 +++++----- + 14 files changed, 560 insertions(+), 49 deletions(-) + create mode 100644 tests/py/inet/snat.t.json.output + +diff --git a/src/json.c b/src/json.c +index 121c11d..3e086f7 100644 +--- a/src/json.c ++++ b/src/json.c +@@ -62,14 +62,10 @@ static int json_array_extend_new(json_t *array, json_t *other_array) + + static void json_add_array_new(json_t *obj, const char *name, json_t *array) + { +- if (json_array_size(array) > 1) { ++ if (json_array_size(array)) + json_object_set_new(obj, name, array); +- } else { +- if (json_array_size(array)) +- json_object_set(obj, name, +- json_array_get(array, 0)); ++ else + json_decref(array); +- } + } + + static json_t *expr_print_json(const struct expr *expr, struct output_ctx *octx) +diff --git a/tests/py/any/log.t.json.output b/tests/py/any/log.t.json.output +index 051c448..bec70a3 100644 +--- a/tests/py/any/log.t.json.output ++++ b/tests/py/any/log.t.json.output +@@ -9,7 +9,20 @@ + [ + { + "log": { +- "flags": "all" ++ "flags": [ ++ "all" ++ ] ++ } ++ } ++] ++ ++# log flags all ++[ ++ { ++ "log": { ++ "flags": [ ++ "all" ++ ] + } + } + ] +diff --git a/tests/py/any/queue.t.json.output b/tests/py/any/queue.t.json.output +index 1104d76..ea37223 100644 +--- a/tests/py/any/queue.t.json.output ++++ b/tests/py/any/queue.t.json.output +@@ -7,3 +7,120 @@ + } + ] + ++# queue num 4-5 fanout ++[ ++ { ++ "queue": { ++ "flags": [ ++ "fanout" ++ ], ++ "num": { ++ "range": [ ++ 4, ++ 5 ++ ] ++ } ++ } ++ } ++] ++ ++# queue num 4-5 bypass ++[ ++ { ++ "queue": { ++ "flags": [ ++ "bypass" ++ ], ++ "num": { ++ "range": [ ++ 4, ++ 5 ++ ] ++ } ++ } ++ } ++] ++ ++# queue flags bypass to numgen inc mod 65536 ++[ ++ { ++ "queue": { ++ "flags": [ ++ "bypass" ++ ], ++ "num": { ++ "numgen": { ++ "mod": 65536, ++ "mode": "inc", ++ "offset": 0 ++ } ++ } ++ } ++ } ++] ++ ++# queue flags bypass to 65535 ++[ ++ { ++ "queue": { ++ "flags": [ ++ "bypass" ++ ], ++ "num": 65535 ++ } ++ } ++] ++ ++# queue flags bypass to 1-65535 ++[ ++ { ++ "queue": { ++ "flags": [ ++ "bypass" ++ ], ++ "num": { ++ "range": [ ++ 1, ++ 65535 ++ ] ++ } ++ } ++ } ++] ++ ++# queue flags bypass to oifname map { "eth0" : 0, "ppp0" : 2, "eth1" : 2 } ++[ ++ { ++ "queue": { ++ "flags": [ ++ "bypass" ++ ], ++ "num": { ++ "map": { ++ "data": { ++ "set": [ ++ [ ++ "eth0", ++ 0 ++ ], ++ [ ++ "ppp0", ++ 2 ++ ], ++ [ ++ "eth1", ++ 2 ++ ] ++ ] ++ }, ++ "key": { ++ "meta": { ++ "key": "oifname" ++ } ++ } ++ } ++ } ++ } ++ } ++] ++ +diff --git a/tests/py/inet/fib.t.json b/tests/py/inet/fib.t.json +index 14a6249..2bfe4f7 100644 +--- a/tests/py/inet/fib.t.json ++++ b/tests/py/inet/fib.t.json +@@ -100,9 +100,7 @@ + "match": { + "left": { + "fib": { +- "flags": [ +- "daddr" +- ], ++ "flags": "daddr", + "result": "check" + } + }, +@@ -118,9 +116,7 @@ + "match": { + "left": { + "fib": { +- "flags": [ +- "daddr" +- ], ++ "flags": "daddr", + "result": "check" + } + }, +diff --git a/tests/py/inet/fib.t.json.output b/tests/py/inet/fib.t.json.output +index e8d0166..d3396dd 100644 +--- a/tests/py/inet/fib.t.json.output ++++ b/tests/py/inet/fib.t.json.output +@@ -43,7 +43,9 @@ + "match": { + "left": { + "fib": { +- "flags": "daddr", ++ "flags": [ ++ "daddr" ++ ], + "result": "check" + } + }, +@@ -59,7 +61,9 @@ + "match": { + "left": { + "fib": { +- "flags": "daddr", ++ "flags": [ ++ "daddr" ++ ], + "result": "check" + } + }, +@@ -69,3 +73,82 @@ + } + ] + ++# fib daddr check vmap { missing : drop, exists : accept } ++[ ++ { ++ "vmap": { ++ "data": { ++ "set": [ ++ [ ++ false, ++ { ++ "drop": null ++ } ++ ], ++ [ ++ true, ++ { ++ "accept": null ++ } ++ ] ++ ] ++ }, ++ "key": { ++ "fib": { ++ "flags": [ ++ "daddr" ++ ], ++ "result": "check" ++ } ++ } ++ } ++ } ++] ++ ++# meta mark set fib daddr check . ct mark map { exists . 0x00000000 : 0x00000001 } ++[ ++ { ++ "mangle": { ++ "key": { ++ "meta": { ++ "key": "mark" ++ } ++ }, ++ "value": { ++ "map": { ++ "data": { ++ "set": [ ++ [ ++ { ++ "concat": [ ++ true, ++ 0 ++ ] ++ }, ++ 1 ++ ] ++ ] ++ }, ++ "key": { ++ "concat": [ ++ { ++ "fib": { ++ "flags": [ ++ "daddr" ++ ], ++ "result": "check" ++ } ++ }, ++ { ++ "ct": { ++ "key": "mark" ++ } ++ } ++ ] ++ } ++ } ++ } ++ } ++ } ++] ++ +diff --git a/tests/py/inet/snat.t.json.output b/tests/py/inet/snat.t.json.output +new file mode 100644 +index 0000000..5b95886 +--- /dev/null ++++ b/tests/py/inet/snat.t.json.output +@@ -0,0 +1,22 @@ ++# iifname "foo" masquerade random ++[ ++ { ++ "match": { ++ "left": { ++ "meta": { ++ "key": "iifname" ++ } ++ }, ++ "op": "==", ++ "right": "foo" ++ } ++ }, ++ { ++ "masquerade": { ++ "flags": [ ++ "random" ++ ] ++ } ++ } ++] ++ +diff --git a/tests/py/inet/synproxy.t.json b/tests/py/inet/synproxy.t.json +index 1dd85a6..b3cec09 100644 +--- a/tests/py/inet/synproxy.t.json ++++ b/tests/py/inet/synproxy.t.json +@@ -19,9 +19,7 @@ + [ + { + "synproxy": { +- "flags": [ +- "timestamp" +- ] ++ "flags": "timestamp" + } + } + ] +@@ -56,9 +54,7 @@ + [ + { + "synproxy": { +- "flags": [ +- "sack-perm" +- ] ++ "flags": "sack-perm" + } + } + ] +diff --git a/tests/py/inet/synproxy.t.json.output b/tests/py/inet/synproxy.t.json.output +index e32cdfb..a1d81bf 100644 +--- a/tests/py/inet/synproxy.t.json.output ++++ b/tests/py/inet/synproxy.t.json.output +@@ -2,7 +2,9 @@ + [ + { + "synproxy": { +- "flags": "timestamp" ++ "flags": [ ++ "timestamp" ++ ] + } + } + ] +@@ -11,7 +13,9 @@ + [ + { + "synproxy": { +- "flags": "sack-perm" ++ "flags": [ ++ "sack-perm" ++ ] + } + } + ] +diff --git a/tests/py/ip/masquerade.t.json.output b/tests/py/ip/masquerade.t.json.output +index 58e7e29..8ca5a42 100644 +--- a/tests/py/ip/masquerade.t.json.output ++++ b/tests/py/ip/masquerade.t.json.output +@@ -121,3 +121,49 @@ + } + ] + ++# udp dport 53 masquerade random ++[ ++ { ++ "match": { ++ "left": { ++ "payload": { ++ "field": "dport", ++ "protocol": "udp" ++ } ++ }, ++ "op": "==", ++ "right": 53 ++ } ++ }, ++ { ++ "masquerade": { ++ "flags": [ ++ "random" ++ ] ++ } ++ } ++] ++ ++# udp dport 53 masquerade persistent ++[ ++ { ++ "match": { ++ "left": { ++ "payload": { ++ "field": "dport", ++ "protocol": "udp" ++ } ++ }, ++ "op": "==", ++ "right": 53 ++ } ++ }, ++ { ++ "masquerade": { ++ "flags": [ ++ "persistent" ++ ] ++ } ++ } ++] ++ +diff --git a/tests/py/ip/redirect.t.json.output b/tests/py/ip/redirect.t.json.output +index 4646c60..09f1e48 100644 +--- a/tests/py/ip/redirect.t.json.output ++++ b/tests/py/ip/redirect.t.json.output +@@ -1,3 +1,49 @@ ++# udp dport 53 redirect random ++[ ++ { ++ "match": { ++ "left": { ++ "payload": { ++ "field": "dport", ++ "protocol": "udp" ++ } ++ }, ++ "op": "==", ++ "right": 53 ++ } ++ }, ++ { ++ "redirect": { ++ "flags": [ ++ "random" ++ ] ++ } ++ } ++] ++ ++# udp dport 53 redirect persistent ++[ ++ { ++ "match": { ++ "left": { ++ "payload": { ++ "field": "dport", ++ "protocol": "udp" ++ } ++ }, ++ "op": "==", ++ "right": 53 ++ } ++ }, ++ { ++ "redirect": { ++ "flags": [ ++ "persistent" ++ ] ++ } ++ } ++] ++ + # udp dport 53 redirect random,persistent,fully-random + [ + { +@@ -144,3 +190,75 @@ + } + ] + ++# tcp dport 9128 redirect to :993 random ++[ ++ { ++ "match": { ++ "left": { ++ "payload": { ++ "field": "dport", ++ "protocol": "tcp" ++ } ++ }, ++ "op": "==", ++ "right": 9128 ++ } ++ }, ++ { ++ "redirect": { ++ "flags": [ ++ "random" ++ ], ++ "port": 993 ++ } ++ } ++] ++ ++# tcp dport 9128 redirect to :993 fully-random ++[ ++ { ++ "match": { ++ "left": { ++ "payload": { ++ "field": "dport", ++ "protocol": "tcp" ++ } ++ }, ++ "op": "==", ++ "right": 9128 ++ } ++ }, ++ { ++ "redirect": { ++ "flags": [ ++ "fully-random" ++ ], ++ "port": 993 ++ } ++ } ++] ++ ++# tcp dport 9128 redirect to :123 persistent ++[ ++ { ++ "match": { ++ "left": { ++ "payload": { ++ "field": "dport", ++ "protocol": "tcp" ++ } ++ }, ++ "op": "==", ++ "right": 9128 ++ } ++ }, ++ { ++ "redirect": { ++ "flags": [ ++ "persistent" ++ ], ++ "port": 123 ++ } ++ } ++] ++ +diff --git a/tests/py/ip/snat.t.json.output b/tests/py/ip/snat.t.json.output +index 2a99780..19eba25 100644 +--- a/tests/py/ip/snat.t.json.output ++++ b/tests/py/ip/snat.t.json.output +@@ -241,8 +241,12 @@ + } + }, + "family": "ip", +- "flags": "netmap", +- "type_flags": "prefix" ++ "flags": [ ++ "netmap" ++ ], ++ "type_flags": [ ++ "prefix" ++ ] + } + } + ] +diff --git a/tests/py/ip6/masquerade.t.json.output b/tests/py/ip6/masquerade.t.json.output +index 31d0cd9..21ed4f6 100644 +--- a/tests/py/ip6/masquerade.t.json.output ++++ b/tests/py/ip6/masquerade.t.json.output +@@ -96,3 +96,49 @@ + } + ] + ++# udp dport 53 masquerade random ++[ ++ { ++ "match": { ++ "left": { ++ "payload": { ++ "field": "dport", ++ "protocol": "udp" ++ } ++ }, ++ "op": "==", ++ "right": 53 ++ } ++ }, ++ { ++ "masquerade": { ++ "flags": [ ++ "random" ++ ] ++ } ++ } ++] ++ ++# udp dport 53 masquerade persistent ++[ ++ { ++ "match": { ++ "left": { ++ "payload": { ++ "field": "dport", ++ "protocol": "udp" ++ } ++ }, ++ "op": "==", ++ "right": 53 ++ } ++ }, ++ { ++ "masquerade": { ++ "flags": [ ++ "persistent" ++ ] ++ } ++ } ++] ++ +diff --git a/tests/py/ip6/redirect.t.json.output b/tests/py/ip6/redirect.t.json.output +index 0174cc7..69c7b03 100644 +--- a/tests/py/ip6/redirect.t.json.output ++++ b/tests/py/ip6/redirect.t.json.output +@@ -144,3 +144,73 @@ + } + ] + ++# udp dport 53 redirect random ++[ ++ { ++ "match": { ++ "left": { ++ "payload": { ++ "field": "dport", ++ "protocol": "udp" ++ } ++ }, ++ "op": "==", ++ "right": 53 ++ } ++ }, ++ { ++ "redirect": { ++ "flags": [ ++ "random" ++ ] ++ } ++ } ++] ++ ++# udp dport 53 redirect persistent ++[ ++ { ++ "match": { ++ "left": { ++ "payload": { ++ "field": "dport", ++ "protocol": "udp" ++ } ++ }, ++ "op": "==", ++ "right": 53 ++ } ++ }, ++ { ++ "redirect": { ++ "flags": [ ++ "persistent" ++ ] ++ } ++ } ++] ++ ++# tcp dport 9128 redirect to :993 random ++[ ++ { ++ "match": { ++ "left": { ++ "payload": { ++ "field": "dport", ++ "protocol": "tcp" ++ } ++ }, ++ "op": "==", ++ "right": 9128 ++ } ++ }, ++ { ++ "redirect": { ++ "flags": [ ++ "random" ++ ], ++ "port": 993 ++ } ++ } ++] ++ +diff --git a/tests/shell/testcases/json/single_flag b/tests/shell/testcases/json/single_flag +index f0a608a..1d70f24 100755 +--- a/tests/shell/testcases/json/single_flag ++++ b/tests/shell/testcases/json/single_flag +@@ -47,10 +47,10 @@ JSON_TABLE_1='{"nftables": [{"table": {"family": "ip", "name": "t", "handle": 0, + JSON_TABLE_1_EQUIV=$(sed 's/\("flags":\) \([^}]*\)/\1 [\2]/' <<< "$JSON_TABLE_1") + + STD_TABLE_2=$(sed 's/\(flags dormant\)/\1,persist/' <<< "$STD_TABLE_1") +-JSON_TABLE_2=$(sed 's/\("flags":\) \("dormant"\)/\1 [\2, "persist"]/' <<< "$JSON_TABLE_1") ++JSON_TABLE_2=$(sed 's/\("dormant"\)/\1, "persist"/' <<< "$JSON_TABLE_1_EQUIV") + +-back_n_forth "$STD_TABLE_1" "$JSON_TABLE_1" +-json_equiv "$JSON_TABLE_1_EQUIV" "$JSON_TABLE_1" ++back_n_forth "$STD_TABLE_1" "$JSON_TABLE_1_EQUIV" ++json_equiv "$JSON_TABLE_1" "$JSON_TABLE_1_EQUIV" + back_n_forth "$STD_TABLE_2" "$JSON_TABLE_2" + + # +@@ -67,10 +67,10 @@ JSON_SET_1='{"nftables": [{"table": {"family": "ip", "name": "t", "handle": 0}}, + JSON_SET_1_EQUIV=$(sed 's/\("flags":\) \([^}]*\)/\1 [\2]/' <<< "$JSON_SET_1") + + STD_SET_2=$(sed 's/\(flags interval\)/\1,timeout/' <<< "$STD_SET_1") +-JSON_SET_2=$(sed 's/\("flags":\) \("interval"\)/\1 [\2, "timeout"]/' <<< "$JSON_SET_1") ++JSON_SET_2=$(sed 's/\("interval"\)/\1, "timeout"/' <<< "$JSON_SET_1_EQUIV") + +-back_n_forth "$STD_SET_1" "$JSON_SET_1" +-json_equiv "$JSON_SET_1_EQUIV" "$JSON_SET_1" ++back_n_forth "$STD_SET_1" "$JSON_SET_1_EQUIV" ++json_equiv "$JSON_SET_1" "$JSON_SET_1_EQUIV" + back_n_forth "$STD_SET_2" "$JSON_SET_2" + + # +@@ -86,10 +86,10 @@ JSON_FIB_1='{"nftables": [{"table": {"family": "ip", "name": "t", "handle": 0}}, + JSON_FIB_1_EQUIV=$(sed 's/\("flags":\) \([^}]*\)/\1 [\2]/' <<< "$JSON_FIB_1") + + STD_FIB_2=$(sed 's/\(fib saddr\)/\1 . iif/' <<< "$STD_FIB_1") +-JSON_FIB_2=$(sed 's/\("flags":\) \("saddr"\)/\1 [\2, "iif"]/' <<< "$JSON_FIB_1") ++JSON_FIB_2=$(sed 's/\("saddr"\)/\1, "iif"/' <<< "$JSON_FIB_1_EQUIV") + +-back_n_forth "$STD_FIB_1" "$JSON_FIB_1" +-json_equiv "$JSON_FIB_1_EQUIV" "$JSON_FIB_1" ++back_n_forth "$STD_FIB_1" "$JSON_FIB_1_EQUIV" ++json_equiv "$JSON_FIB_1" "$JSON_FIB_1_EQUIV" + back_n_forth "$STD_FIB_2" "$JSON_FIB_2" + + # +@@ -105,10 +105,10 @@ JSON_NAT_1='{"nftables": [{"table": {"family": "ip", "name": "t", "handle": 0}}, + JSON_NAT_1_EQUIV=$(sed 's/\("flags":\) \([^}]*\)/\1 [\2]/' <<< "$JSON_NAT_1") + + STD_NAT_2=$(sed 's/\(persistent\)/random,\1/' <<< "$STD_NAT_1") +-JSON_NAT_2=$(sed 's/\("flags":\) \("persistent"\)/\1 ["random", \2]/' <<< "$JSON_NAT_1") ++JSON_NAT_2=$(sed 's/\("persistent"\)/"random", \1/' <<< "$JSON_NAT_1_EQUIV") + +-back_n_forth "$STD_NAT_1" "$JSON_NAT_1" +-json_equiv "$JSON_NAT_1_EQUIV" "$JSON_NAT_1" ++back_n_forth "$STD_NAT_1" "$JSON_NAT_1_EQUIV" ++json_equiv "$JSON_NAT_1" "$JSON_NAT_1_EQUIV" + back_n_forth "$STD_NAT_2" "$JSON_NAT_2" + + # +@@ -124,10 +124,10 @@ JSON_LOG_1='{"nftables": [{"table": {"family": "ip", "name": "t", "handle": 0}}, + JSON_LOG_1_EQUIV=$(sed 's/\("flags":\) \([^}]*\)/\1 [\2]/' <<< "$JSON_LOG_1") + + STD_LOG_2=$(sed 's/\(tcp sequence\)/\1,options/' <<< "$STD_LOG_1") +-JSON_LOG_2=$(sed 's/\("flags":\) \("tcp sequence"\)/\1 [\2, "tcp options"]/' <<< "$JSON_LOG_1") ++JSON_LOG_2=$(sed 's/\("tcp sequence"\)/\1, "tcp options"/' <<< "$JSON_LOG_1_EQUIV") + +-back_n_forth "$STD_LOG_1" "$JSON_LOG_1" +-json_equiv "$JSON_LOG_1_EQUIV" "$JSON_LOG_1" ++back_n_forth "$STD_LOG_1" "$JSON_LOG_1_EQUIV" ++json_equiv "$JSON_LOG_1" "$JSON_LOG_1_EQUIV" + back_n_forth "$STD_LOG_2" "$JSON_LOG_2" + + # +@@ -143,10 +143,10 @@ JSON_SYNPROXY_1='{"nftables": [{"table": {"family": "ip", "name": "t", "handle": + JSON_SYNPROXY_1_EQUIV=$(sed 's/\("flags":\) \([^}]*\)/\1 [\2]/' <<< "$JSON_SYNPROXY_1") + + STD_SYNPROXY_2=$(sed 's/\(sack-perm\)/timestamp \1/' <<< "$STD_SYNPROXY_1") +-JSON_SYNPROXY_2=$(sed 's/\("flags":\) \("sack-perm"\)/\1 ["timestamp", \2]/' <<< "$JSON_SYNPROXY_1") ++JSON_SYNPROXY_2=$(sed 's/\("sack-perm"\)/"timestamp", \1/' <<< "$JSON_SYNPROXY_1_EQUIV") + +-back_n_forth "$STD_SYNPROXY_1" "$JSON_SYNPROXY_1" +-json_equiv "$JSON_SYNPROXY_1_EQUIV" "$JSON_SYNPROXY_1" ++back_n_forth "$STD_SYNPROXY_1" "$JSON_SYNPROXY_1_EQUIV" ++json_equiv "$JSON_SYNPROXY_1" "$JSON_SYNPROXY_1_EQUIV" + back_n_forth "$STD_SYNPROXY_2" "$JSON_SYNPROXY_2" + + # +@@ -164,10 +164,10 @@ JSON_SYNPROXY_OBJ_1='{"nftables": [{"table": {"family": "ip", "name": "t", "hand + JSON_SYNPROXY_OBJ_1_EQUIV=$(sed 's/\("flags":\) \([^}]*\)/\1 [\2]/' <<< "$JSON_SYNPROXY_OBJ_1") + + STD_SYNPROXY_OBJ_2=$(sed 's/ \(sack-perm\)/timestamp \1/' <<< "$STD_SYNPROXY_OBJ_1") +-JSON_SYNPROXY_OBJ_2=$(sed 's/\("flags":\) \("sack-perm"\)/\1 ["timestamp", \2]/' <<< "$JSON_SYNPROXY_OBJ_1") ++JSON_SYNPROXY_OBJ_2=$(sed 's/\("sack-perm"\)/"timestamp", \1/' <<< "$JSON_SYNPROXY_OBJ_1_EQUIV") + +-back_n_forth "$STD_SYNPROXY_OBJ_1" "$JSON_SYNPROXY_OBJ_1" +-json_equiv "$JSON_SYNPROXY_OBJ_1_EQUIV" "$JSON_SYNPROXY_OBJ_1" ++back_n_forth "$STD_SYNPROXY_OBJ_1" "$JSON_SYNPROXY_OBJ_1_EQUIV" ++json_equiv "$JSON_SYNPROXY_OBJ_1" "$JSON_SYNPROXY_OBJ_1_EQUIV" + back_n_forth "$STD_SYNPROXY_OBJ_2" "$JSON_SYNPROXY_OBJ_2" + + # +@@ -183,8 +183,8 @@ JSON_QUEUE_1='{"nftables": [{"table": {"family": "ip", "name": "t", "handle": 0} + JSON_QUEUE_1_EQUIV=$(sed 's/\("flags":\) \([^}]*\)/\1 [\2]/' <<< "$JSON_QUEUE_1") + + STD_QUEUE_2=$(sed 's/\(bypass\)/\1,fanout/' <<< "$STD_QUEUE_1") +-JSON_QUEUE_2=$(sed 's/\("flags":\) \("bypass"\)/\1 [\2, "fanout"]/' <<< "$JSON_QUEUE_1") ++JSON_QUEUE_2=$(sed 's/\("bypass"\)/\1, "fanout"/' <<< "$JSON_QUEUE_1_EQUIV") + +-back_n_forth "$STD_QUEUE_1" "$JSON_QUEUE_1" +-json_equiv "$JSON_QUEUE_1_EQUIV" "$JSON_QUEUE_1" ++back_n_forth "$STD_QUEUE_1" "$JSON_QUEUE_1_EQUIV" ++json_equiv "$JSON_QUEUE_1" "$JSON_QUEUE_1_EQUIV" + back_n_forth "$STD_QUEUE_2" "$JSON_QUEUE_2" diff --git a/0245-segtree-incorrect-type-when-aggregating-concatenated.patch b/0245-segtree-incorrect-type-when-aggregating-concatenated.patch new file mode 100644 index 0000000..38cc57b --- /dev/null +++ b/0245-segtree-incorrect-type-when-aggregating-concatenated.patch @@ -0,0 +1,44 @@ +From c144fcc1a60e961676850677e3f01a875ec140d0 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:14:43 +0200 +Subject: [PATCH] segtree: incorrect type when aggregating concatenated set + ranges + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit c44995e31ba60c4823bb81fecd29020452b9e702 + +commit c44995e31ba60c4823bb81fecd29020452b9e702 +Author: Pablo Neira Ayuso +Date: Wed Aug 13 15:19:23 2025 +0200 + + segtree: incorrect type when aggregating concatenated set ranges + + Uncovered by the compound_expr_remove() replacement by type safe function + coming after this patch. + + Add expression to the concatenation which is reachable via expr_value(). + + This bug is subtle, I could not spot any reproducible buggy behaviour + when using the wrong type when running the existing tests. + + Fixes: 8ac2f3b2fca3 ("src: Add support for concatenated set ranges") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/segtree.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/segtree.c b/src/segtree.c +index ee68ce1..291d877 100644 +--- a/src/segtree.c ++++ b/src/segtree.c +@@ -448,7 +448,7 @@ next: + mpz_clear(range); + + r2 = list_entry(r2_next, typeof(*r2), list); +- compound_expr_remove(start, r1); ++ compound_expr_remove(expr_value(start), r1); + + if (free_r1) + expr_free(r1); diff --git a/0246-src-ensure-chain-policy-evaluation-when-specified.patch b/0246-src-ensure-chain-policy-evaluation-when-specified.patch new file mode 100644 index 0000000..d4f36b4 --- /dev/null +++ b/0246-src-ensure-chain-policy-evaluation-when-specified.patch @@ -0,0 +1,154 @@ +From d850ec82b0c6f3ee5ff762d283c1cec1283503f7 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:14:43 +0200 +Subject: [PATCH] src: ensure chain policy evaluation when specified + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 01277922fede9fef8aacf5cc871bfbd55bbd78ef + +commit 01277922fede9fef8aacf5cc871bfbd55bbd78ef +Author: Pablo Neira Ayuso +Date: Sun Aug 17 21:01:30 2025 +0200 + + src: ensure chain policy evaluation when specified + + Set on CHAIN_F_BASECHAIN when policy is specified in chain, otherwise + chain priority is not evaluated. + + Toggling this flag requires needs three adjustments to work though: + + 1) chain_evaluate() needs skip evaluation of hook name and priority if + not specified to allow for updating the default chain policy, e.g. + + chain ip x y { policy accept; } + + 2) update netlink bytecode generation for chain to skip NFTA_CHAIN_HOOK + so update path is exercised in the kernel. + + 3) error reporting needs to check if basechain priority and type is + set on, otherwise skip further hints. + + Fixes: acdfae9c3126 ("src: allow to specify the default policy for base chains") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/cmd.c | 3 +++ + src/evaluate.c | 27 ++++++++++--------- + src/mnl.c | 8 ++++-- + src/parser_bison.y | 1 + + .../bogons/nft-f/basechain_bad_policy | 2 ++ + .../bogons/nft-f/unexisting_chain_set_policy | 5 ++++ + 6 files changed, 32 insertions(+), 14 deletions(-) + create mode 100644 tests/shell/testcases/bogons/nft-f/basechain_bad_policy + create mode 100644 tests/shell/testcases/bogons/nft-f/unexisting_chain_set_policy + +diff --git a/src/cmd.c b/src/cmd.c +index 6f6da35..76124dc 100644 +--- a/src/cmd.c ++++ b/src/cmd.c +@@ -266,6 +266,9 @@ static int nft_cmd_chain_error(struct netlink_ctx *ctx, struct cmd *cmd, + if (!(chain->flags & CHAIN_F_BASECHAIN)) + break; + ++ if (!chain->priority.expr || !chain->type.str) ++ break; ++ + mpz_export_data(&priority, chain->priority.expr->value, + BYTEORDER_HOST_ENDIAN, sizeof(int)); + if (priority <= -200 && !strcmp(chain->type.str, "nat")) +diff --git a/src/evaluate.c b/src/evaluate.c +index 73cd3c0..93f0bbf 100644 +--- a/src/evaluate.c ++++ b/src/evaluate.c +@@ -5205,18 +5205,21 @@ static int chain_evaluate(struct eval_ctx *ctx, struct chain *chain) + } + + if (chain->flags & CHAIN_F_BASECHAIN) { +- chain->hook.num = str2hooknum(chain->handle.family, +- chain->hook.name); +- if (chain->hook.num == NF_INET_NUMHOOKS) +- return __stmt_binary_error(ctx, &chain->hook.loc, NULL, +- "The %s family does not support this hook", +- family2str(chain->handle.family)); +- +- if (!evaluate_priority(ctx, &chain->priority, +- chain->handle.family, chain->hook.num)) +- return __stmt_binary_error(ctx, &chain->priority.loc, NULL, +- "invalid priority expression %s in this context.", +- expr_name(chain->priority.expr)); ++ if (chain->hook.name) { ++ chain->hook.num = str2hooknum(chain->handle.family, ++ chain->hook.name); ++ if (chain->hook.num == NF_INET_NUMHOOKS) ++ return __stmt_binary_error(ctx, &chain->hook.loc, NULL, ++ "The %s family does not support this hook", ++ family2str(chain->handle.family)); ++ } ++ if (chain->priority.expr) { ++ if (!evaluate_priority(ctx, &chain->priority, ++ chain->handle.family, chain->hook.num)) ++ return __stmt_binary_error(ctx, &chain->priority.loc, NULL, ++ "invalid priority expression %s in this context.", ++ expr_name(chain->priority.expr)); ++ } + if (chain->policy) { + expr_set_context(&ctx->ectx, &policy_type, + NFT_NAME_MAXLEN * BITS_PER_BYTE); +diff --git a/src/mnl.c b/src/mnl.c +index 199820d..78d02cc 100644 +--- a/src/mnl.c ++++ b/src/mnl.c +@@ -875,7 +875,9 @@ int mnl_nft_chain_add(struct netlink_ctx *ctx, struct cmd *cmd, + mnl_attr_put_strz(nlh, NFTA_CHAIN_TYPE, cmd->chain->type.str); + } + +- nest = mnl_attr_nest_start(nlh, NFTA_CHAIN_HOOK); ++ if (cmd->chain->type.str || ++ (cmd->chain && cmd->chain->dev_expr)) ++ nest = mnl_attr_nest_start(nlh, NFTA_CHAIN_HOOK); + + if (cmd->chain->type.str) { + mnl_attr_put_u32(nlh, NFTA_HOOK_HOOKNUM, htonl(cmd->chain->hook.num)); +@@ -887,7 +889,9 @@ int mnl_nft_chain_add(struct netlink_ctx *ctx, struct cmd *cmd, + if (cmd->chain && cmd->chain->dev_expr) + mnl_nft_chain_devs_build(nlh, cmd); + +- mnl_attr_nest_end(nlh, nest); ++ if (cmd->chain->type.str || ++ (cmd->chain && cmd->chain->dev_expr)) ++ mnl_attr_nest_end(nlh, nest); + } + + nftnl_chain_free(nlc); +diff --git a/src/parser_bison.y b/src/parser_bison.y +index 62cd895..a5b70d8 100644 +--- a/src/parser_bison.y ++++ b/src/parser_bison.y +@@ -2797,6 +2797,7 @@ policy_spec : POLICY policy_expr close_scope_policy + } + $0->policy = $2; + $0->policy->location = @$; ++ $0->flags |= CHAIN_F_BASECHAIN; + } + ; + +diff --git a/tests/shell/testcases/bogons/nft-f/basechain_bad_policy b/tests/shell/testcases/bogons/nft-f/basechain_bad_policy +new file mode 100644 +index 0000000..998e423 +--- /dev/null ++++ b/tests/shell/testcases/bogons/nft-f/basechain_bad_policy +@@ -0,0 +1,2 @@ ++define MY_POLICY = deny ++table T { chain C { policy $MY_POLICY; };}; +diff --git a/tests/shell/testcases/bogons/nft-f/unexisting_chain_set_policy b/tests/shell/testcases/bogons/nft-f/unexisting_chain_set_policy +new file mode 100644 +index 0000000..0889559 +--- /dev/null ++++ b/tests/shell/testcases/bogons/nft-f/unexisting_chain_set_policy +@@ -0,0 +1,5 @@ ++table ip x { ++ chain y { ++ policy drop; ++ } ++} diff --git a/0247-fib-restore-JSON-output-for-relational-expressions.patch b/0247-fib-restore-JSON-output-for-relational-expressions.patch new file mode 100644 index 0000000..2cba71c --- /dev/null +++ b/0247-fib-restore-JSON-output-for-relational-expressions.patch @@ -0,0 +1,198 @@ +From cdb37b165a4bb02d5c522170fd530f3b4fa55440 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:14:43 +0200 +Subject: [PATCH] fib: restore JSON output for relational expressions + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit dbb92d3137d9ed684cb450ab3c2aef881a4e6006 +Conflicts: Dropped changes to non-existent .json-nft dump + +commit dbb92d3137d9ed684cb450ab3c2aef881a4e6006 +Author: Pablo Neira Ayuso +Date: Tue Aug 19 11:23:42 2025 +0200 + + fib: restore JSON output for relational expressions + + JSON output for the fib expression changed: + + - "result": "check" + + "result": "oif" + + This breaks third party JSON parsers, revert this change for relational + expressions only via workaround until there are clear rules on how to + proceed with JSON schema updates. + + As for set and map statements, keep this new "check" result type since + it is not possible to peek on rhs in such case to guess if the + NFT_FIB_F_PRESENT flag needs to be set on. + + Closes: https://bugzilla.netfilter.org/show_bug.cgi?id=1806 + Fixes: f4b646032acf ("fib: allow to check if route exists in maps") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + include/fib.h | 2 +- + src/fib.c | 7 ++++--- + src/json.c | 20 ++++++++++++++----- + tests/py/inet/fib.t.json | 4 ++-- + tests/py/inet/fib.t.json.output | 4 ++-- + tests/shell/testcases/json/single_flag | 2 +- + tests/shell/testcases/transactions/0049huge_0 | 2 +- + 7 files changed, 26 insertions(+), 15 deletions(-) + +diff --git a/include/fib.h b/include/fib.h +index 07bb221..4e39ec9 100644 +--- a/include/fib.h ++++ b/include/fib.h +@@ -3,7 +3,7 @@ + + #include + +-extern const char *fib_result_str(const struct expr *expr); ++extern const char *fib_result_str(const struct expr *expr, bool check); + extern struct expr *fib_expr_alloc(const struct location *loc, + unsigned int flags, + unsigned int result); +diff --git a/src/fib.c b/src/fib.c +index e28c522..5383613 100644 +--- a/src/fib.c ++++ b/src/fib.c +@@ -53,13 +53,14 @@ const struct datatype fib_addr_type = { + .sym_tbl = &addrtype_tbl, + }; + +-const char *fib_result_str(const struct expr *expr) ++const char *fib_result_str(const struct expr *expr, bool check) + { + enum nft_fib_result result = expr->fib.result; + uint32_t flags = expr->fib.flags; + + /* Exception: check if route exists. */ +- if (result == NFT_FIB_RESULT_OIF && ++ if (check && ++ result == NFT_FIB_RESULT_OIF && + flags & NFTA_FIB_F_PRESENT) + return "check"; + +@@ -95,7 +96,7 @@ static void fib_expr_print(const struct expr *expr, struct output_ctx *octx) + if (flags) + nft_print(octx, "0x%x", flags); + +- nft_print(octx, " %s", fib_result_str(expr)); ++ nft_print(octx, " %s", fib_result_str(expr, true)); + } + + static bool fib_expr_cmp(const struct expr *e1, const struct expr *e2) +diff --git a/src/json.c b/src/json.c +index 3e086f7..8a56b08 100644 +--- a/src/json.c ++++ b/src/json.c +@@ -585,12 +585,21 @@ json_t *binop_expr_json(const struct expr *expr, struct output_ctx *octx) + __binop_expr_json(expr->op, expr, octx)); + } + ++/* Workaround to retain backwards compatibility in fib output. */ ++#define __NFT_CTX_OUTPUT_RELATIONAL (1 << 30) ++ + json_t *relational_expr_json(const struct expr *expr, struct output_ctx *octx) + { +- return nft_json_pack("{s:{s:s, s:o, s:o}}", "match", +- "op", expr_op_symbols[expr->op] ? : "in", +- "left", expr_print_json(expr->left, octx), +- "right", expr_print_json(expr->right, octx)); ++ json_t *ret; ++ ++ octx->flags |= __NFT_CTX_OUTPUT_RELATIONAL; ++ ret = nft_json_pack("{s:{s:s, s:o, s:o}}", "match", ++ "op", expr_op_symbols[expr->op] ? : "in", ++ "left", expr_print_json(expr->left, octx), ++ "right", expr_print_json(expr->right, octx)); ++ octx->flags &= ~__NFT_CTX_OUTPUT_RELATIONAL; ++ ++ return ret; + } + + json_t *range_expr_json(const struct expr *expr, struct output_ctx *octx) +@@ -939,9 +948,10 @@ json_t *fib_expr_json(const struct expr *expr, struct output_ctx *octx) + { + const char *fib_flags[] = { "saddr", "daddr", "mark", "iif", "oif" }; + unsigned int flags = expr->fib.flags & ~NFTA_FIB_F_PRESENT; ++ bool check = !(octx->flags & __NFT_CTX_OUTPUT_RELATIONAL); + json_t *root; + +- root = nft_json_pack("{s:s}", "result", fib_result_str(expr)); ++ root = nft_json_pack("{s:s}", "result", fib_result_str(expr, check)); + + if (flags) { + json_t *tmp = json_array(); +diff --git a/tests/py/inet/fib.t.json b/tests/py/inet/fib.t.json +index 2bfe4f7..b6953c8 100644 +--- a/tests/py/inet/fib.t.json ++++ b/tests/py/inet/fib.t.json +@@ -101,7 +101,7 @@ + "left": { + "fib": { + "flags": "daddr", +- "result": "check" ++ "result": "oif" + } + }, + "op": "==", +@@ -117,7 +117,7 @@ + "left": { + "fib": { + "flags": "daddr", +- "result": "check" ++ "result": "oif" + } + }, + "op": "==", +diff --git a/tests/py/inet/fib.t.json.output b/tests/py/inet/fib.t.json.output +index d3396dd..18a13b3 100644 +--- a/tests/py/inet/fib.t.json.output ++++ b/tests/py/inet/fib.t.json.output +@@ -46,7 +46,7 @@ + "flags": [ + "daddr" + ], +- "result": "check" ++ "result": "oif" + } + }, + "op": "==", +@@ -64,7 +64,7 @@ + "flags": [ + "daddr" + ], +- "result": "check" ++ "result": "oif" + } + }, + "op": "==", +diff --git a/tests/shell/testcases/json/single_flag b/tests/shell/testcases/json/single_flag +index 1d70f24..fa917eb 100755 +--- a/tests/shell/testcases/json/single_flag ++++ b/tests/shell/testcases/json/single_flag +@@ -82,7 +82,7 @@ STD_FIB_1="table ip t { + fib saddr check exists + } + }" +-JSON_FIB_1='{"nftables": [{"table": {"family": "ip", "name": "t", "handle": 0}}, {"chain": {"family": "ip", "table": "t", "name": "c", "handle": 0}}, {"rule": {"family": "ip", "table": "t", "chain": "c", "handle": 0, "expr": [{"match": {"op": "==", "left": {"fib": {"result": "check", "flags": "saddr"}}, "right": true}}]}}]}' ++JSON_FIB_1='{"nftables": [{"table": {"family": "ip", "name": "t", "handle": 0}}, {"chain": {"family": "ip", "table": "t", "name": "c", "handle": 0}}, {"rule": {"family": "ip", "table": "t", "chain": "c", "handle": 0, "expr": [{"match": {"op": "==", "left": {"fib": {"result": "oif", "flags": "saddr"}}, "right": true}}]}}]}' + JSON_FIB_1_EQUIV=$(sed 's/\("flags":\) \([^}]*\)/\1 [\2]/' <<< "$JSON_FIB_1") + + STD_FIB_2=$(sed 's/\(fib saddr\)/\1 . iif/' <<< "$STD_FIB_1") +diff --git a/tests/shell/testcases/transactions/0049huge_0 b/tests/shell/testcases/transactions/0049huge_0 +index 90049b6..f66953c 100755 +--- a/tests/shell/testcases/transactions/0049huge_0 ++++ b/tests/shell/testcases/transactions/0049huge_0 +@@ -47,7 +47,7 @@ fi + $NFT flush ruleset + + RULESET='{"nftables": [{"metainfo": {"json_schema_version": 1}}, {"add": {"table": {"family": "inet", "name": "firewalld"}}}, {"add": {"table": {"family": "ip", "name": "firewalld"}}}, {"add": {"table": {"family": "ip6", "name": "firewalld"}}}, +-{"add": {"chain": {"family": "inet", "table": "firewalld", "name": "raw_PREROUTING", "type": "filter", "hook": "prerouting", "prio": -290}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "raw_PREROUTING_ZONES"}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "raw_PREROUTING", "expr": [{"jump": {"target": "raw_PREROUTING_ZONES"}}]}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "mangle_PREROUTING", "type": "filter", "hook": "prerouting", "prio": -140}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "mangle_PREROUTING_ZONES"}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "mangle_PREROUTING", "expr": [{"jump": {"target": "mangle_PREROUTING_ZONES"}}]}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_PREROUTING", "type": "nat", "hook": "prerouting", "prio": -90}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_PREROUTING_ZONES"}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_PREROUTING", "expr": [{"jump": {"target": "nat_PREROUTING_ZONES"}}]}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_POSTROUTING", "type": "nat", "hook": "postrouting", "prio": 110}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_POSTROUTING_ZONES"}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_POSTROUTING", "expr": [{"jump": {"target": "nat_POSTROUTING_ZONES"}}]}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_PREROUTING", "type": "nat", "hook": "prerouting", "prio": -90}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_PREROUTING_ZONES"}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_PREROUTING", "expr": [{"jump": {"target": "nat_PREROUTING_ZONES"}}]}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_POSTROUTING", "type": "nat", "hook": "postrouting", "prio": 110}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_POSTROUTING_ZONES"}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_POSTROUTING", "expr": [{"jump": {"target": "nat_POSTROUTING_ZONES"}}]}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_INPUT", "type": "filter", "hook": "input", "prio": 10}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FORWARD", "type": "filter", "hook": "forward", "prio": 10}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_OUTPUT", "type": "filter", "hook": "output", "prio": 10}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_INPUT_ZONES"}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_INPUT", "expr": [{"match": {"left": {"ct": {"key": "state"}}, "op": "in", "right": {"set": ["established", "related"]}}}, {"accept": null}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_INPUT", "expr": [{"match": {"left": {"ct": {"key": "status"}}, "op": "in", "right": "dnat"}}, {"accept": null}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_INPUT", "expr": [{"match": {"left": {"meta": {"key": "iifname"}}, "op": "==", "right": "lo"}}, {"accept": null}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_INPUT", "expr": [{"jump": {"target": "filter_INPUT_ZONES"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_INPUT", "expr": [{"match": {"left": {"ct": {"key": "state"}}, "op": "in", "right": {"set": ["invalid"]}}}, {"drop": null}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_INPUT", "expr": [{"reject": {"type": "icmpx", "expr": "admin-prohibited"}}]}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FORWARD_IN_ZONES"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FORWARD_OUT_ZONES"}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FORWARD", "expr": [{"match": {"left": {"ct": {"key": "state"}}, "op": "in", "right": {"set": ["established", "related"]}}}, {"accept": null}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FORWARD", "expr": [{"match": {"left": {"ct": {"key": "status"}}, "op": "in", "right": "dnat"}}, {"accept": null}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FORWARD", "expr": [{"match": {"left": {"meta": {"key": "iifname"}}, "op": "==", "right": "lo"}}, {"accept": null}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FORWARD", "expr": [{"jump": {"target": "filter_FORWARD_IN_ZONES"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FORWARD", "expr": [{"jump": {"target": "filter_FORWARD_OUT_ZONES"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FORWARD", "expr": [{"match": {"left": {"ct": {"key": "state"}}, "op": "in", "right": {"set": ["invalid"]}}}, {"drop": null}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FORWARD", "expr": [{"reject": {"type": "icmpx", "expr": "admin-prohibited"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_OUTPUT", "expr": [{"match": {"left": {"meta": {"key": "oifname"}}, "op": "==", "right": "lo"}}, {"accept": null}]}}}, {"insert": {"rule": {"family": "inet", "table": "firewalld", "chain": "raw_PREROUTING", "expr": [{"match": {"left": {"meta": {"key": "nfproto"}}, "op": "==", "right": "ipv6"}}, {"match": {"left": {"fib": {"flags": ["saddr", "iif"], "result": "check"}}, "op": "==", "right": false}}, {"drop": null}]}}}, {"insert": {"rule": {"family": "inet", "table": "firewalld", "chain": "raw_PREROUTING", "expr": [{"match": {"left": {"payload": {"protocol": "icmpv6", "field": "type"}}, "op": "==", "right": {"set": ["nd-router-advert", "nd-neighbor-solicit"]}}}, {"accept": null}]}}}, ++{"add": {"chain": {"family": "inet", "table": "firewalld", "name": "raw_PREROUTING", "type": "filter", "hook": "prerouting", "prio": -290}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "raw_PREROUTING_ZONES"}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "raw_PREROUTING", "expr": [{"jump": {"target": "raw_PREROUTING_ZONES"}}]}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "mangle_PREROUTING", "type": "filter", "hook": "prerouting", "prio": -140}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "mangle_PREROUTING_ZONES"}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "mangle_PREROUTING", "expr": [{"jump": {"target": "mangle_PREROUTING_ZONES"}}]}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_PREROUTING", "type": "nat", "hook": "prerouting", "prio": -90}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_PREROUTING_ZONES"}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_PREROUTING", "expr": [{"jump": {"target": "nat_PREROUTING_ZONES"}}]}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_POSTROUTING", "type": "nat", "hook": "postrouting", "prio": 110}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_POSTROUTING_ZONES"}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_POSTROUTING", "expr": [{"jump": {"target": "nat_POSTROUTING_ZONES"}}]}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_PREROUTING", "type": "nat", "hook": "prerouting", "prio": -90}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_PREROUTING_ZONES"}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_PREROUTING", "expr": [{"jump": {"target": "nat_PREROUTING_ZONES"}}]}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_POSTROUTING", "type": "nat", "hook": "postrouting", "prio": 110}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_POSTROUTING_ZONES"}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_POSTROUTING", "expr": [{"jump": {"target": "nat_POSTROUTING_ZONES"}}]}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_INPUT", "type": "filter", "hook": "input", "prio": 10}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FORWARD", "type": "filter", "hook": "forward", "prio": 10}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_OUTPUT", "type": "filter", "hook": "output", "prio": 10}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_INPUT_ZONES"}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_INPUT", "expr": [{"match": {"left": {"ct": {"key": "state"}}, "op": "in", "right": {"set": ["established", "related"]}}}, {"accept": null}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_INPUT", "expr": [{"match": {"left": {"ct": {"key": "status"}}, "op": "in", "right": "dnat"}}, {"accept": null}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_INPUT", "expr": [{"match": {"left": {"meta": {"key": "iifname"}}, "op": "==", "right": "lo"}}, {"accept": null}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_INPUT", "expr": [{"jump": {"target": "filter_INPUT_ZONES"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_INPUT", "expr": [{"match": {"left": {"ct": {"key": "state"}}, "op": "in", "right": {"set": ["invalid"]}}}, {"drop": null}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_INPUT", "expr": [{"reject": {"type": "icmpx", "expr": "admin-prohibited"}}]}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FORWARD_IN_ZONES"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FORWARD_OUT_ZONES"}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FORWARD", "expr": [{"match": {"left": {"ct": {"key": "state"}}, "op": "in", "right": {"set": ["established", "related"]}}}, {"accept": null}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FORWARD", "expr": [{"match": {"left": {"ct": {"key": "status"}}, "op": "in", "right": "dnat"}}, {"accept": null}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FORWARD", "expr": [{"match": {"left": {"meta": {"key": "iifname"}}, "op": "==", "right": "lo"}}, {"accept": null}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FORWARD", "expr": [{"jump": {"target": "filter_FORWARD_IN_ZONES"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FORWARD", "expr": [{"jump": {"target": "filter_FORWARD_OUT_ZONES"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FORWARD", "expr": [{"match": {"left": {"ct": {"key": "state"}}, "op": "in", "right": {"set": ["invalid"]}}}, {"drop": null}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FORWARD", "expr": [{"reject": {"type": "icmpx", "expr": "admin-prohibited"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_OUTPUT", "expr": [{"match": {"left": {"meta": {"key": "oifname"}}, "op": "==", "right": "lo"}}, {"accept": null}]}}}, {"insert": {"rule": {"family": "inet", "table": "firewalld", "chain": "raw_PREROUTING", "expr": [{"match": {"left": {"meta": {"key": "nfproto"}}, "op": "==", "right": "ipv6"}}, {"match": {"left": {"fib": {"flags": ["saddr", "iif"], "result": "oif"}}, "op": "==", "right": false}}, {"drop": null}]}}}, {"insert": {"rule": {"family": "inet", "table": "firewalld", "chain": "raw_PREROUTING", "expr": [{"match": {"left": {"payload": {"protocol": "icmpv6", "field": "type"}}, "op": "==", "right": {"set": ["nd-router-advert", "nd-neighbor-solicit"]}}}, {"accept": null}]}}}, + {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_OUTPUT", "index": 0, "expr": [{"match": {"left": {"payload": {"protocol": "ip6", "field": "daddr"}}, "op": "==", "right": {"set": [{"prefix": {"addr": "::0.0.0.0", "len": 96}}, {"prefix": {"addr": "::ffff:0.0.0.0", "len": 96}}, {"prefix": {"addr": "2002:0000::", "len": 24}}, {"prefix": {"addr": "2002:0a00::", "len": 24}}, {"prefix": {"addr": "2002:7f00::", "len": 24}}, {"prefix": {"addr": "2002:ac10::", "len": 28}}, {"prefix": {"addr": "2002:c0a8::", "len": 32}}, {"prefix": {"addr": "2002:a9fe::", "len": 32}}, {"prefix": {"addr": "2002:e000::", "len": 19}}]}}}, {"reject": {"type": "icmpv6", "expr": "addr-unreachable"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FORWARD", "index": 2, "expr": [{"match": {"left": {"payload": {"protocol": "ip6", "field": "daddr"}}, "op": "==", "right": {"set": [{"prefix": {"addr": "::0.0.0.0", "len": 96}}, {"prefix": {"addr": "::ffff:0.0.0.0", "len": 96}}, {"prefix": {"addr": "2002:0000::", "len": 24}}, {"prefix": {"addr": "2002:0a00::", "len": 24}}, {"prefix": {"addr": "2002:7f00::", "len": 24}}, {"prefix": {"addr": "2002:ac10::", "len": 28}}, {"prefix": {"addr": "2002:c0a8::", "len": 32}}, {"prefix": {"addr": "2002:a9fe::", "len": 32}}, {"prefix": {"addr": "2002:e000::", "len": 19}}]}}}, {"reject": {"type": "icmpv6", "expr": "addr-unreachable"}}]}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "raw_PRE_public"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "raw_PRE_public_pre"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "raw_PRE_public_log"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "raw_PRE_public_deny"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "raw_PRE_public_allow"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "raw_PRE_public_post"}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "raw_PRE_public", "expr": [{"jump": {"target": "raw_PRE_public_pre"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "raw_PRE_public", "expr": [{"jump": {"target": "raw_PRE_public_log"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "raw_PRE_public", "expr": [{"jump": {"target": "raw_PRE_public_deny"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "raw_PRE_public", "expr": [{"jump": {"target": "raw_PRE_public_allow"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "raw_PRE_public", "expr": [{"jump": {"target": "raw_PRE_public_post"}}]}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_IN_public"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_IN_public_pre"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_IN_public_log"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_IN_public_deny"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_IN_public_allow"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_IN_public_post"}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_IN_public", "expr": [{"jump": {"target": "filter_IN_public_pre"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_IN_public", "expr": [{"jump": {"target": "filter_IN_public_log"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_IN_public", "expr": [{"jump": {"target": "filter_IN_public_deny"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_IN_public", "expr": [{"jump": {"target": "filter_IN_public_allow"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_IN_public", "expr": [{"jump": {"target": "filter_IN_public_post"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_IN_public_allow", "expr": [{"match": {"left": {"payload": {"protocol": "tcp", "field": "dport"}}, "op": "==", "right": 22}}, {"match": {"left": {"ct": {"key": "state"}}, "op": "in", "right": {"set": ["new", "untracked"]}}}, {"accept": null}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_IN_public_allow", "expr": [{"match": {"left": {"payload": {"protocol": "ip6", "field": "daddr"}}, "op": "==", "right": {"prefix": {"addr": "fe80::", "len": 64}}}}, {"match": {"left": {"payload": {"protocol": "udp", "field": "dport"}}, "op": "==", "right": 546}}, {"match": {"left": {"ct": {"key": "state"}}, "op": "in", "right": {"set": ["new", "untracked"]}}}, {"accept": null}]}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDI_public"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDI_public_pre"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDI_public_log"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDI_public_deny"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDI_public_allow"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDI_public_post"}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDI_public", "expr": [{"jump": {"target": "filter_FWDI_public_pre"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDI_public", "expr": [{"jump": {"target": "filter_FWDI_public_log"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDI_public", "expr": [{"jump": {"target": "filter_FWDI_public_deny"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDI_public", "expr": [{"jump": {"target": "filter_FWDI_public_allow"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDI_public", "expr": [{"jump": {"target": "filter_FWDI_public_post"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_IN_public", "index": 4, "expr": [{"match": {"left": {"meta": {"key": "l4proto"}}, "op": "==", "right": {"set": ["icmp", "icmpv6"]}}}, {"accept": null}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDI_public", "index": 4, "expr": [{"match": {"left": {"meta": {"key": "l4proto"}}, "op": "==", "right": {"set": ["icmp", "icmpv6"]}}}, {"accept": null}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "raw_PREROUTING_ZONES", "expr": [{"goto": {"target": "raw_PRE_public"}}]}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "mangle_PRE_public"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "mangle_PRE_public_pre"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "mangle_PRE_public_log"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "mangle_PRE_public_deny"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "mangle_PRE_public_allow"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "mangle_PRE_public_post"}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "mangle_PRE_public", "expr": [{"jump": {"target": "mangle_PRE_public_pre"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "mangle_PRE_public", "expr": [{"jump": {"target": "mangle_PRE_public_log"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "mangle_PRE_public", "expr": [{"jump": {"target": "mangle_PRE_public_deny"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "mangle_PRE_public", "expr": [{"jump": {"target": "mangle_PRE_public_allow"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "mangle_PRE_public", "expr": [{"jump": {"target": "mangle_PRE_public_post"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "mangle_PREROUTING_ZONES", "expr": [{"goto": {"target": "mangle_PRE_public"}}]}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_PRE_public"}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_PRE_public_pre"}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_PRE_public_log"}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_PRE_public_deny"}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_PRE_public_allow"}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_PRE_public_post"}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_PRE_public", "expr": [{"jump": {"target": "nat_PRE_public_pre"}}]}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_PRE_public", "expr": [{"jump": {"target": "nat_PRE_public_log"}}]}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_PRE_public", "expr": [{"jump": {"target": "nat_PRE_public_deny"}}]}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_PRE_public", "expr": [{"jump": {"target": "nat_PRE_public_allow"}}]}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_PRE_public", "expr": [{"jump": {"target": "nat_PRE_public_post"}}]}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_PRE_public"}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_PRE_public_pre"}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_PRE_public_log"}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_PRE_public_deny"}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_PRE_public_allow"}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_PRE_public_post"}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_PRE_public", "expr": [{"jump": {"target": "nat_PRE_public_pre"}}]}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_PRE_public", "expr": [{"jump": {"target": "nat_PRE_public_log"}}]}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_PRE_public", "expr": [{"jump": {"target": "nat_PRE_public_deny"}}]}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_PRE_public", "expr": [{"jump": {"target": "nat_PRE_public_allow"}}]}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_PRE_public", "expr": [{"jump": {"target": "nat_PRE_public_post"}}]}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_PREROUTING_ZONES", "expr": [{"goto": {"target": "nat_PRE_public"}}]}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_PREROUTING_ZONES", "expr": [{"goto": {"target": "nat_PRE_public"}}]}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_POST_public"}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_POST_public_pre"}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_POST_public_log"}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_POST_public_deny"}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_POST_public_allow"}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_POST_public_post"}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_POST_public", "expr": [{"jump": {"target": "nat_POST_public_pre"}}]}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_POST_public", "expr": [{"jump": {"target": "nat_POST_public_log"}}]}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_POST_public", "expr": [{"jump": {"target": "nat_POST_public_deny"}}]}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_POST_public", "expr": [{"jump": {"target": "nat_POST_public_allow"}}]}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_POST_public", "expr": [{"jump": {"target": "nat_POST_public_post"}}]}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_POST_public"}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_POST_public_pre"}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_POST_public_log"}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_POST_public_deny"}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_POST_public_allow"}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_POST_public_post"}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_POST_public", "expr": [{"jump": {"target": "nat_POST_public_pre"}}]}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_POST_public", "expr": [{"jump": {"target": "nat_POST_public_log"}}]}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_POST_public", "expr": [{"jump": {"target": "nat_POST_public_deny"}}]}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_POST_public", "expr": [{"jump": {"target": "nat_POST_public_allow"}}]}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_POST_public", "expr": [{"jump": {"target": "nat_POST_public_post"}}]}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_POSTROUTING_ZONES", "expr": [{"goto": {"target": "nat_POST_public"}}]}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_POSTROUTING_ZONES", "expr": [{"goto": {"target": "nat_POST_public"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_INPUT_ZONES", "expr": [{"goto": {"target": "filter_IN_public"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FORWARD_IN_ZONES", "expr": [{"goto": {"target": "filter_FWDI_public"}}]}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDO_public"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDO_public_pre"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDO_public_log"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDO_public_deny"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDO_public_allow"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDO_public_post"}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDO_public", "expr": [{"jump": {"target": "filter_FWDO_public_pre"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDO_public", "expr": [{"jump": {"target": "filter_FWDO_public_log"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDO_public", "expr": [{"jump": {"target": "filter_FWDO_public_deny"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDO_public", "expr": [{"jump": {"target": "filter_FWDO_public_allow"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDO_public", "expr": [{"jump": {"target": "filter_FWDO_public_post"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FORWARD_OUT_ZONES", "expr": [{"goto": {"target": "filter_FWDO_public"}}]}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "raw_PRE_trusted"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "raw_PRE_trusted_pre"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "raw_PRE_trusted_log"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "raw_PRE_trusted_deny"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "raw_PRE_trusted_allow"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "raw_PRE_trusted_post"}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "raw_PRE_trusted", "expr": [{"jump": {"target": "raw_PRE_trusted_pre"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "raw_PRE_trusted", "expr": [{"jump": {"target": "raw_PRE_trusted_log"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "raw_PRE_trusted", "expr": [{"jump": {"target": "raw_PRE_trusted_deny"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "raw_PRE_trusted", "expr": [{"jump": {"target": "raw_PRE_trusted_allow"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "raw_PRE_trusted", "expr": [{"jump": {"target": "raw_PRE_trusted_post"}}]}}}, {"insert": {"rule": {"family": "inet", "table": "firewalld", "chain": "raw_PREROUTING_ZONES", "expr": [{"match": {"left": {"meta": {"key": "iifname"}}, "op": "==", "right": "perm_dummy2"}}, {"goto": {"target": "raw_PRE_trusted"}}]}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "mangle_PRE_trusted"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "mangle_PRE_trusted_pre"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "mangle_PRE_trusted_log"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "mangle_PRE_trusted_deny"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "mangle_PRE_trusted_allow"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "mangle_PRE_trusted_post"}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "mangle_PRE_trusted", "expr": [{"jump": {"target": "mangle_PRE_trusted_pre"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "mangle_PRE_trusted", "expr": [{"jump": {"target": "mangle_PRE_trusted_log"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "mangle_PRE_trusted", "expr": [{"jump": {"target": "mangle_PRE_trusted_deny"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "mangle_PRE_trusted", "expr": [{"jump": {"target": "mangle_PRE_trusted_allow"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "mangle_PRE_trusted", "expr": [{"jump": {"target": "mangle_PRE_trusted_post"}}]}}}, {"insert": {"rule": {"family": "inet", "table": "firewalld", "chain": "mangle_PREROUTING_ZONES", "expr": [{"match": {"left": {"meta": {"key": "iifname"}}, "op": "==", "right": "perm_dummy2"}}, {"goto": {"target": "mangle_PRE_trusted"}}]}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_PRE_trusted"}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_PRE_trusted_pre"}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_PRE_trusted_log"}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_PRE_trusted_deny"}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_PRE_trusted_allow"}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_PRE_trusted_post"}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_PRE_trusted", "expr": [{"jump": {"target": "nat_PRE_trusted_pre"}}]}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_PRE_trusted", "expr": [{"jump": {"target": "nat_PRE_trusted_log"}}]}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_PRE_trusted", "expr": [{"jump": {"target": "nat_PRE_trusted_deny"}}]}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_PRE_trusted", "expr": [{"jump": {"target": "nat_PRE_trusted_allow"}}]}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_PRE_trusted", "expr": [{"jump": {"target": "nat_PRE_trusted_post"}}]}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_PRE_trusted"}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_PRE_trusted_pre"}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_PRE_trusted_log"}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_PRE_trusted_deny"}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_PRE_trusted_allow"}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_PRE_trusted_post"}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_PRE_trusted", "expr": [{"jump": {"target": "nat_PRE_trusted_pre"}}]}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_PRE_trusted", "expr": [{"jump": {"target": "nat_PRE_trusted_log"}}]}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_PRE_trusted", "expr": [{"jump": {"target": "nat_PRE_trusted_deny"}}]}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_PRE_trusted", "expr": [{"jump": {"target": "nat_PRE_trusted_allow"}}]}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_PRE_trusted", "expr": [{"jump": {"target": "nat_PRE_trusted_post"}}]}}}, {"insert": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_PREROUTING_ZONES", "expr": [{"match": {"left": {"meta": {"key": "iifname"}}, "op": "==", "right": "perm_dummy2"}}, {"goto": {"target": "nat_PRE_trusted"}}]}}}, {"insert": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_PREROUTING_ZONES", "expr": [{"match": {"left": {"meta": {"key": "iifname"}}, "op": "==", "right": "perm_dummy2"}}, {"goto": {"target": "nat_PRE_trusted"}}]}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_POST_trusted"}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_POST_trusted_pre"}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_POST_trusted_log"}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_POST_trusted_deny"}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_POST_trusted_allow"}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_POST_trusted_post"}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_POST_trusted", "expr": [{"jump": {"target": "nat_POST_trusted_pre"}}]}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_POST_trusted", "expr": [{"jump": {"target": "nat_POST_trusted_log"}}]}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_POST_trusted", "expr": [{"jump": {"target": "nat_POST_trusted_deny"}}]}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_POST_trusted", "expr": [{"jump": {"target": "nat_POST_trusted_allow"}}]}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_POST_trusted", "expr": [{"jump": {"target": "nat_POST_trusted_post"}}]}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_POST_trusted"}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_POST_trusted_pre"}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_POST_trusted_log"}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_POST_trusted_deny"}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_POST_trusted_allow"}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_POST_trusted_post"}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_POST_trusted", "expr": [{"jump": {"target": "nat_POST_trusted_pre"}}]}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_POST_trusted", "expr": [{"jump": {"target": "nat_POST_trusted_log"}}]}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_POST_trusted", "expr": [{"jump": {"target": "nat_POST_trusted_deny"}}]}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_POST_trusted", "expr": [{"jump": {"target": "nat_POST_trusted_allow"}}]}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_POST_trusted", "expr": [{"jump": {"target": "nat_POST_trusted_post"}}]}}}, {"insert": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_POSTROUTING_ZONES", "expr": [{"match": {"left": {"meta": {"key": "oifname"}}, "op": "==", "right": "perm_dummy2"}}, {"goto": {"target": "nat_POST_trusted"}}]}}}, {"insert": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_POSTROUTING_ZONES", "expr": [{"match": {"left": {"meta": {"key": "oifname"}}, "op": "==", "right": "perm_dummy2"}}, {"goto": {"target": "nat_POST_trusted"}}]}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_IN_trusted"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_IN_trusted_pre"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_IN_trusted_log"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_IN_trusted_deny"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_IN_trusted_allow"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_IN_trusted_post"}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_IN_trusted", "expr": [{"jump": {"target": "filter_IN_trusted_pre"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_IN_trusted", "expr": [{"jump": {"target": "filter_IN_trusted_log"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_IN_trusted", "expr": [{"jump": {"target": "filter_IN_trusted_deny"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_IN_trusted", "expr": [{"jump": {"target": "filter_IN_trusted_allow"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_IN_trusted", "expr": [{"jump": {"target": "filter_IN_trusted_post"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_IN_trusted", "expr": [{"accept": null}]}}}, {"insert": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_INPUT_ZONES", "expr": [{"match": {"left": {"meta": {"key": "iifname"}}, "op": "==", "right": "perm_dummy2"}}, {"goto": {"target": "filter_IN_trusted"}}]}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDI_trusted"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDI_trusted_pre"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDI_trusted_log"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDI_trusted_deny"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDI_trusted_allow"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDI_trusted_post"}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDI_trusted", "expr": [{"jump": {"target": "filter_FWDI_trusted_pre"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDI_trusted", "expr": [{"jump": {"target": "filter_FWDI_trusted_log"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDI_trusted", "expr": [{"jump": {"target": "filter_FWDI_trusted_deny"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDI_trusted", "expr": [{"jump": {"target": "filter_FWDI_trusted_allow"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDI_trusted", "expr": [{"jump": {"target": "filter_FWDI_trusted_post"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDI_trusted", "expr": [{"accept": null}]}}}, {"insert": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FORWARD_IN_ZONES", "expr": [{"match": {"left": {"meta": {"key": "iifname"}}, "op": "==", "right": "perm_dummy2"}}, {"goto": {"target": "filter_FWDI_trusted"}}]}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDO_trusted"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDO_trusted_pre"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDO_trusted_log"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDO_trusted_deny"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDO_trusted_allow"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDO_trusted_post"}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDO_trusted", "expr": [{"jump": {"target": "filter_FWDO_trusted_pre"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDO_trusted", "expr": [{"jump": {"target": "filter_FWDO_trusted_log"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDO_trusted", "expr": [{"jump": {"target": "filter_FWDO_trusted_deny"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDO_trusted", "expr": [{"jump": {"target": "filter_FWDO_trusted_allow"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDO_trusted", "expr": [{"jump": {"target": "filter_FWDO_trusted_post"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDO_trusted", "expr": [{"accept": null}]}}}, {"insert": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FORWARD_OUT_ZONES", "expr": [{"match": {"left": {"meta": {"key": "oifname"}}, "op": "==", "right": "perm_dummy2"}}, {"goto": {"target": "filter_FWDO_trusted"}}]}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "raw_PRE_work"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "raw_PRE_work_pre"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "raw_PRE_work_log"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "raw_PRE_work_deny"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "raw_PRE_work_allow"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "raw_PRE_work_post"}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "raw_PRE_work", "expr": [{"jump": {"target": "raw_PRE_work_pre"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "raw_PRE_work", "expr": [{"jump": {"target": "raw_PRE_work_log"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "raw_PRE_work", "expr": [{"jump": {"target": "raw_PRE_work_deny"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "raw_PRE_work", "expr": [{"jump": {"target": "raw_PRE_work_allow"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "raw_PRE_work", "expr": [{"jump": {"target": "raw_PRE_work_post"}}]}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_IN_work"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_IN_work_pre"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_IN_work_log"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_IN_work_deny"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_IN_work_allow"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_IN_work_post"}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_IN_work", "expr": [{"jump": {"target": "filter_IN_work_pre"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_IN_work", "expr": [{"jump": {"target": "filter_IN_work_log"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_IN_work", "expr": [{"jump": {"target": "filter_IN_work_deny"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_IN_work", "expr": [{"jump": {"target": "filter_IN_work_allow"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_IN_work", "expr": [{"jump": {"target": "filter_IN_work_post"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_IN_work_allow", "expr": [{"match": {"left": {"payload": {"protocol": "tcp", "field": "dport"}}, "op": "==", "right": 22}}, {"match": {"left": {"ct": {"key": "state"}}, "op": "in", "right": {"set": ["new", "untracked"]}}}, {"accept": null}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_IN_work_allow", "expr": [{"match": {"left": {"payload": {"protocol": "ip6", "field": "daddr"}}, "op": "==", "right": {"prefix": {"addr": "fe80::", "len": 64}}}}, {"match": {"left": {"payload": {"protocol": "udp", "field": "dport"}}, "op": "==", "right": 546}}, {"match": {"left": {"ct": {"key": "state"}}, "op": "in", "right": {"set": ["new", "untracked"]}}}, {"accept": null}]}}}, {"insert": {"rule": {"family": "inet", "table": "firewalld", "chain": "raw_PREROUTING_ZONES", "expr": [{"match": {"left": {"meta": {"key": "iifname"}}, "op": "==", "right": "perm_dummy"}}, {"goto": {"target": "raw_PRE_work"}}]}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "mangle_PRE_work"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "mangle_PRE_work_pre"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "mangle_PRE_work_log"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "mangle_PRE_work_deny"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "mangle_PRE_work_allow"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "mangle_PRE_work_post"}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "mangle_PRE_work", "expr": [{"jump": {"target": "mangle_PRE_work_pre"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "mangle_PRE_work", "expr": [{"jump": {"target": "mangle_PRE_work_log"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "mangle_PRE_work", "expr": [{"jump": {"target": "mangle_PRE_work_deny"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "mangle_PRE_work", "expr": [{"jump": {"target": "mangle_PRE_work_allow"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "mangle_PRE_work", "expr": [{"jump": {"target": "mangle_PRE_work_post"}}]}}}, {"insert": {"rule": {"family": "inet", "table": "firewalld", "chain": "mangle_PREROUTING_ZONES", "expr": [{"match": {"left": {"meta": {"key": "iifname"}}, "op": "==", "right": "perm_dummy"}}, {"goto": {"target": "mangle_PRE_work"}}]}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_PRE_work"}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_PRE_work_pre"}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_PRE_work_log"}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_PRE_work_deny"}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_PRE_work_allow"}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_PRE_work_post"}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_PRE_work", "expr": [{"jump": {"target": "nat_PRE_work_pre"}}]}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_PRE_work", "expr": [{"jump": {"target": "nat_PRE_work_log"}}]}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_PRE_work", "expr": [{"jump": {"target": "nat_PRE_work_deny"}}]}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_PRE_work", "expr": [{"jump": {"target": "nat_PRE_work_allow"}}]}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_PRE_work", "expr": [{"jump": {"target": "nat_PRE_work_post"}}]}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_PRE_work"}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_PRE_work_pre"}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_PRE_work_log"}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_PRE_work_deny"}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_PRE_work_allow"}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_PRE_work_post"}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_PRE_work", "expr": [{"jump": {"target": "nat_PRE_work_pre"}}]}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_PRE_work", "expr": [{"jump": {"target": "nat_PRE_work_log"}}]}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_PRE_work", "expr": [{"jump": {"target": "nat_PRE_work_deny"}}]}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_PRE_work", "expr": [{"jump": {"target": "nat_PRE_work_allow"}}]}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_PRE_work", "expr": [{"jump": {"target": "nat_PRE_work_post"}}]}}}, {"insert": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_PREROUTING_ZONES", "expr": [{"match": {"left": {"meta": {"key": "iifname"}}, "op": "==", "right": "perm_dummy"}}, {"goto": {"target": "nat_PRE_work"}}]}}}, {"insert": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_PREROUTING_ZONES", "expr": [{"match": {"left": {"meta": {"key": "iifname"}}, "op": "==", "right": "perm_dummy"}}, {"goto": {"target": "nat_PRE_work"}}]}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_POST_work"}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_POST_work_pre"}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_POST_work_log"}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_POST_work_deny"}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_POST_work_allow"}}}, {"add": {"chain": {"family": "ip", "table": "firewalld", "name": "nat_POST_work_post"}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_POST_work", "expr": [{"jump": {"target": "nat_POST_work_pre"}}]}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_POST_work", "expr": [{"jump": {"target": "nat_POST_work_log"}}]}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_POST_work", "expr": [{"jump": {"target": "nat_POST_work_deny"}}]}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_POST_work", "expr": [{"jump": {"target": "nat_POST_work_allow"}}]}}}, {"add": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_POST_work", "expr": [{"jump": {"target": "nat_POST_work_post"}}]}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_POST_work"}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_POST_work_pre"}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_POST_work_log"}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_POST_work_deny"}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_POST_work_allow"}}}, {"add": {"chain": {"family": "ip6", "table": "firewalld", "name": "nat_POST_work_post"}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_POST_work", "expr": [{"jump": {"target": "nat_POST_work_pre"}}]}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_POST_work", "expr": [{"jump": {"target": "nat_POST_work_log"}}]}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_POST_work", "expr": [{"jump": {"target": "nat_POST_work_deny"}}]}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_POST_work", "expr": [{"jump": {"target": "nat_POST_work_allow"}}]}}}, {"add": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_POST_work", "expr": [{"jump": {"target": "nat_POST_work_post"}}]}}}, {"insert": {"rule": {"family": "ip", "table": "firewalld", "chain": "nat_POSTROUTING_ZONES", "expr": [{"match": {"left": {"meta": {"key": "oifname"}}, "op": "==", "right": "perm_dummy"}}, {"goto": {"target": "nat_POST_work"}}]}}}, {"insert": {"rule": {"family": "ip6", "table": "firewalld", "chain": "nat_POSTROUTING_ZONES", "expr": [{"match": {"left": {"meta": {"key": "oifname"}}, "op": "==", "right": "perm_dummy"}}, {"goto": {"target": "nat_POST_work"}}]}}}, {"insert": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_INPUT_ZONES", "expr": [{"match": {"left": {"meta": {"key": "iifname"}}, "op": "==", "right": "perm_dummy"}}, {"goto": {"target": "filter_IN_work"}}]}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDI_work"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDI_work_pre"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDI_work_log"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDI_work_deny"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDI_work_allow"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDI_work_post"}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDI_work", "expr": [{"jump": {"target": "filter_FWDI_work_pre"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDI_work", "expr": [{"jump": {"target": "filter_FWDI_work_log"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDI_work", "expr": [{"jump": {"target": "filter_FWDI_work_deny"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDI_work", "expr": [{"jump": {"target": "filter_FWDI_work_allow"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDI_work", "expr": [{"jump": {"target": "filter_FWDI_work_post"}}]}}}, {"insert": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FORWARD_IN_ZONES", "expr": [{"match": {"left": {"meta": {"key": "iifname"}}, "op": "==", "right": "perm_dummy"}}, {"goto": {"target": "filter_FWDI_work"}}]}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDO_work"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDO_work_pre"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDO_work_log"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDO_work_deny"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDO_work_allow"}}}, {"add": {"chain": {"family": "inet", "table": "firewalld", "name": "filter_FWDO_work_post"}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDO_work", "expr": [{"jump": {"target": "filter_FWDO_work_pre"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDO_work", "expr": [{"jump": {"target": "filter_FWDO_work_log"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDO_work", "expr": [{"jump": {"target": "filter_FWDO_work_deny"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDO_work", "expr": [{"jump": {"target": "filter_FWDO_work_allow"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDO_work", "expr": [{"jump": {"target": "filter_FWDO_work_post"}}]}}}, {"insert": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FORWARD_OUT_ZONES", "expr": [{"match": {"left": {"meta": {"key": "oifname"}}, "op": "==", "right": "perm_dummy"}}, {"goto": {"target": "filter_FWDO_work"}}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_IN_work", "index": 4, "expr": [{"match": {"left": {"meta": {"key": "l4proto"}}, "op": "==", "right": {"set": ["icmp", "icmpv6"]}}}, {"accept": null}]}}}, {"add": {"rule": {"family": "inet", "table": "firewalld", "chain": "filter_FWDI_work", "index": 4, "expr": [{"match": {"left": {"meta": {"key": "l4proto"}}, "op": "==", "right": {"set": ["icmp", "icmpv6"]}}}, {"accept": null}]}}}]}' + + if [ "$NFT_TEST_HAVE_json" != n ]; then diff --git a/0248-mnl-silence-compiler-warning.patch b/0248-mnl-silence-compiler-warning.patch new file mode 100644 index 0000000..1211ca4 --- /dev/null +++ b/0248-mnl-silence-compiler-warning.patch @@ -0,0 +1,55 @@ +From d9cb07c0161ed0de935b92a8f2383e2de4bfc320 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:15:07 +0200 +Subject: [PATCH] mnl: silence compiler warning + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit cd9f168875aca72caacdcfb552c15b6484a82bd5 + +commit cd9f168875aca72caacdcfb552c15b6484a82bd5 +Author: Florian Westphal +Date: Wed Aug 20 14:44:43 2025 +0200 + + mnl: silence compiler warning + + gcc 14.3.0 reports this: + + src/mnl.c: In function 'mnl_nft_chain_add': + src/mnl.c:916:25: warning: 'nest' may be used uninitialized [-Wmaybe-uninitialized] + 916 | mnl_attr_nest_end(nlh, nest); + + I guess its because compiler can't know that the conditions cannot change + in-between and assumes nest_end() can be called without nest_start(). + + Fixes: 01277922fede ("src: ensure chain policy evaluation when specified") + Signed-off-by: Florian Westphal + Reviewed-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/mnl.c | 5 ++--- + 1 file changed, 2 insertions(+), 3 deletions(-) + +diff --git a/src/mnl.c b/src/mnl.c +index 78d02cc..061b74f 100644 +--- a/src/mnl.c ++++ b/src/mnl.c +@@ -868,7 +868,7 @@ int mnl_nft_chain_add(struct netlink_ctx *ctx, struct cmd *cmd, + nftnl_chain_nlmsg_build_payload(nlh, nlc); + + if (cmd->chain && cmd->chain->flags & CHAIN_F_BASECHAIN) { +- struct nlattr *nest; ++ struct nlattr *nest = NULL; + + if (cmd->chain->type.str) { + cmd_add_loc(cmd, nlh, &cmd->chain->type.loc); +@@ -889,8 +889,7 @@ int mnl_nft_chain_add(struct netlink_ctx *ctx, struct cmd *cmd, + if (cmd->chain && cmd->chain->dev_expr) + mnl_nft_chain_devs_build(nlh, cmd); + +- if (cmd->chain->type.str || +- (cmd->chain && cmd->chain->dev_expr)) ++ if (nest) + mnl_attr_nest_end(nlh, nest); + } + diff --git a/0249-tests-monitor-Fix-for-flag-arrays-in-JSON-output.patch b/0249-tests-monitor-Fix-for-flag-arrays-in-JSON-output.patch new file mode 100644 index 0000000..7713ac6 --- /dev/null +++ b/0249-tests-monitor-Fix-for-flag-arrays-in-JSON-output.patch @@ -0,0 +1,119 @@ +From 500db255360239cfac48ad63692b123f1219a94e Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:15:07 +0200 +Subject: [PATCH] tests: monitor: Fix for flag arrays in JSON output + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 428462077f415fe49619ec5d6cbf7477ee5359bc + +commit 428462077f415fe49619ec5d6cbf7477ee5359bc +Author: Phil Sutter +Date: Sat Aug 23 23:57:28 2025 +0200 + + tests: monitor: Fix for flag arrays in JSON output + + Missed to adjust the expected JSON output in this test suite, too. + + Fixes: 5e492307c2c93 ("json: Do not reduce single-item arrays on output") + Signed-off-by: Phil Sutter + +Signed-off-by: Phil Sutter +--- + tests/monitor/testcases/map-expr.t | 2 +- + tests/monitor/testcases/set-concat-interval.t | 2 +- + tests/monitor/testcases/set-interval.t | 2 +- + tests/monitor/testcases/set-maps.t | 2 +- + tests/monitor/testcases/set-mixed.t | 2 +- + tests/monitor/testcases/set-multiple.t | 4 ++-- + tests/monitor/testcases/set-simple.t | 2 +- + 7 files changed, 8 insertions(+), 8 deletions(-) + +diff --git a/tests/monitor/testcases/map-expr.t b/tests/monitor/testcases/map-expr.t +index 9042004..d11ad0e 100644 +--- a/tests/monitor/testcases/map-expr.t ++++ b/tests/monitor/testcases/map-expr.t +@@ -3,4 +3,4 @@ I add table ip t + I add map ip t m { typeof meta day . meta hour : verdict; flags interval; counter; } + O - + J {"add": {"table": {"family": "ip", "name": "t", "handle": 0}}} +-J {"add": {"map": {"family": "ip", "name": "m", "table": "t", "type": {"typeof": {"concat": [{"meta": {"key": "day"}}, {"meta": {"key": "hour"}}]}}, "handle": 0, "map": "verdict", "flags": "interval", "stmt": [{"counter": null}]}}} ++J {"add": {"map": {"family": "ip", "name": "m", "table": "t", "type": {"typeof": {"concat": [{"meta": {"key": "day"}}, {"meta": {"key": "hour"}}]}}, "handle": 0, "map": "verdict", "flags": ["interval"], "stmt": [{"counter": null}]}}} +diff --git a/tests/monitor/testcases/set-concat-interval.t b/tests/monitor/testcases/set-concat-interval.t +index a42682f..3542b82 100644 +--- a/tests/monitor/testcases/set-concat-interval.t ++++ b/tests/monitor/testcases/set-concat-interval.t +@@ -10,6 +10,6 @@ I add map ip t s { typeof udp length . @ih,32,32 : verdict; flags interval; elem + O add map ip t s { typeof udp length . @ih,32,32 : verdict; flags interval; } + O add element ip t s { 20-80 . 0x14 : accept } + O add element ip t s { 1-10 . 0xa : drop } +-J {"add": {"map": {"family": "ip", "name": "s", "table": "t", "type": {"typeof": {"concat": [{"payload": {"protocol": "udp", "field": "length"}}, {"payload": {"base": "ih", "offset": 32, "len": 32}}]}}, "handle": 0, "map": "verdict", "flags": "interval"}}} ++J {"add": {"map": {"family": "ip", "name": "s", "table": "t", "type": {"typeof": {"concat": [{"payload": {"protocol": "udp", "field": "length"}}, {"payload": {"base": "ih", "offset": 32, "len": 32}}]}}, "handle": 0, "map": "verdict", "flags": ["interval"]}}} + J {"add": {"element": {"family": "ip", "table": "t", "name": "s", "elem": {"set": [[{"concat": [{"range": [20, 80]}, 20]}, {"accept": null}]]}}}} + J {"add": {"element": {"family": "ip", "table": "t", "name": "s", "elem": {"set": [[{"concat": [{"range": [1, 10]}, 10]}, {"drop": null}]]}}}} +diff --git a/tests/monitor/testcases/set-interval.t b/tests/monitor/testcases/set-interval.t +index 84cf98c..5053c59 100644 +--- a/tests/monitor/testcases/set-interval.t ++++ b/tests/monitor/testcases/set-interval.t +@@ -10,7 +10,7 @@ I add set ip t s { type inet_service; flags interval; elements = { 20, 30-40 }; + O add set ip t s { type inet_service; flags interval; } + O add element ip t s { 20 } + O add element ip t s { 30-40 } +-J {"add": {"set": {"family": "ip", "name": "s", "table": "t", "type": "inet_service", "handle": 0, "flags": "interval"}}} ++J {"add": {"set": {"family": "ip", "name": "s", "table": "t", "type": "inet_service", "handle": 0, "flags": ["interval"]}}} + J {"add": {"element": {"family": "ip", "table": "t", "name": "s", "elem": {"set": [20]}}}} + J {"add": {"element": {"family": "ip", "table": "t", "name": "s", "elem": {"set": [{"range": [30, 40]}]}}}} + +diff --git a/tests/monitor/testcases/set-maps.t b/tests/monitor/testcases/set-maps.t +index aaf332f..acda480 100644 +--- a/tests/monitor/testcases/set-maps.t ++++ b/tests/monitor/testcases/set-maps.t +@@ -3,7 +3,7 @@ I add table ip t + I add map ip t portip { type inet_service: ipv4_addr; flags interval; } + O - + J {"add": {"table": {"family": "ip", "name": "t", "handle": 0}}} +-J {"add": {"map": {"family": "ip", "name": "portip", "table": "t", "type": "inet_service", "handle": 0, "map": "ipv4_addr", "flags": "interval"}}} ++J {"add": {"map": {"family": "ip", "name": "portip", "table": "t", "type": "inet_service", "handle": 0, "map": "ipv4_addr", "flags": ["interval"]}}} + + I add element ip t portip { 80-100: 10.0.0.1 } + O - +diff --git a/tests/monitor/testcases/set-mixed.t b/tests/monitor/testcases/set-mixed.t +index 1cf3d38..08c2011 100644 +--- a/tests/monitor/testcases/set-mixed.t ++++ b/tests/monitor/testcases/set-mixed.t +@@ -4,7 +4,7 @@ I add set ip t portrange { type inet_service; flags interval; } + I add set ip t ports { type inet_service; } + O - + J {"add": {"table": {"family": "ip", "name": "t", "handle": 0}}} +-J {"add": {"set": {"family": "ip", "name": "portrange", "table": "t", "type": "inet_service", "handle": 0, "flags": "interval"}}} ++J {"add": {"set": {"family": "ip", "name": "portrange", "table": "t", "type": "inet_service", "handle": 0, "flags": ["interval"]}}} + J {"add": {"set": {"family": "ip", "name": "ports", "table": "t", "type": "inet_service", "handle": 0}}} + + # make sure concurrent adds work +diff --git a/tests/monitor/testcases/set-multiple.t b/tests/monitor/testcases/set-multiple.t +index 84de98e..bd7a624 100644 +--- a/tests/monitor/testcases/set-multiple.t ++++ b/tests/monitor/testcases/set-multiple.t +@@ -4,8 +4,8 @@ I add set ip t portrange { type inet_service; flags interval; } + I add set ip t portrange2 { type inet_service; flags interval; } + O - + J {"add": {"table": {"family": "ip", "name": "t", "handle": 0}}} +-J {"add": {"set": {"family": "ip", "name": "portrange", "table": "t", "type": "inet_service", "handle": 0, "flags": "interval"}}} +-J {"add": {"set": {"family": "ip", "name": "portrange2", "table": "t", "type": "inet_service", "handle": 0, "flags": "interval"}}} ++J {"add": {"set": {"family": "ip", "name": "portrange", "table": "t", "type": "inet_service", "handle": 0, "flags": ["interval"]}}} ++J {"add": {"set": {"family": "ip", "name": "portrange2", "table": "t", "type": "inet_service", "handle": 0, "flags": ["interval"]}}} + + # make sure concurrent adds work + I add element ip t portrange { 1024-65535 } +diff --git a/tests/monitor/testcases/set-simple.t b/tests/monitor/testcases/set-simple.t +index 4bef144..6853a0e 100644 +--- a/tests/monitor/testcases/set-simple.t ++++ b/tests/monitor/testcases/set-simple.t +@@ -3,7 +3,7 @@ I add table ip t + I add set ip t portrange { type inet_service; flags interval; } + O - + J {"add": {"table": {"family": "ip", "name": "t", "handle": 0}}} +-J {"add": {"set": {"family": "ip", "name": "portrange", "table": "t", "type": "inet_service", "handle": 0, "flags": "interval"}}} ++J {"add": {"set": {"family": "ip", "name": "portrange", "table": "t", "type": "inet_service", "handle": 0, "flags": ["interval"]}}} + + # adding some ranges + I add element ip t portrange { 1-10 } diff --git a/0250-mnl-continue-on-ENOBUFS-errors-when-processing-batch.patch b/0250-mnl-continue-on-ENOBUFS-errors-when-processing-batch.patch new file mode 100644 index 0000000..eab8dac --- /dev/null +++ b/0250-mnl-continue-on-ENOBUFS-errors-when-processing-batch.patch @@ -0,0 +1,89 @@ +From d3737f3a3f7bd6822768b1922d8c5dbf8dd7bd48 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:15:07 +0200 +Subject: [PATCH] mnl: continue on ENOBUFS errors when processing batch + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 47e9aaf0227daf16f43a7442e1dceae8851817a5 + +commit 47e9aaf0227daf16f43a7442e1dceae8851817a5 +Author: Pablo Neira Ayuso +Date: Tue Aug 26 10:09:13 2025 +0200 + + mnl: continue on ENOBUFS errors when processing batch + + A user reports that: + + nft -f ruleset.nft + + fails with: + + netlink: Error: Could not process rule: No buffer space available + + This was triggered by: + + table ip6 fule { + set domestic_ip6 { + type ipv6_addr + flags dynamic,interval + elements = $domestic_ip6 + } + chain prerouting { + type filter hook prerouting priority 0; + ip6 daddr @domestic_ip6 counter + } + } + + where $domestic_ip6 contains a large number of IPv6 addresses. + + This set declaration is not supported currently, because dynamic sets + with intervals are not supported, then every IPv6 address that is added + triggers an error, overruning the userspace socket buffer with lots of + NLMSG_ERROR messages (or too big NLMSG_ERROR message to fit into the + socket buffer). + + In the particular context of batch processing, ENOBUFS is just an + indication that too many errors have occurred. The kernel cannot store + any more NLMSG_ERROR messages into the userspace socket buffer. + + However, there are still NLMSG_ERROR messages in the socket buffer to be + processed that can provide a hint on what is going on. + + Instead of breaking on ENOBUFS in batches, continue error processing. + + After this patch, the ruleset above displays: + + ruleset.nft:2367:7-18: Error: Could not process rule: Operation not supported + set domestic_ip6 { + ^^^^^^^^^^^^ + ruleset.nft:2367:7-18: Error: Could not process rule: No such file or directory + set domestic_ip6 { + ^^^^^^^^^^^^ + + Fixes: a72315d2bad4 ("src: add rule batching support") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/mnl.c | 7 ++++++- + 1 file changed, 6 insertions(+), 1 deletion(-) + +diff --git a/src/mnl.c b/src/mnl.c +index 061b74f..ec423e2 100644 +--- a/src/mnl.c ++++ b/src/mnl.c +@@ -445,8 +445,13 @@ int mnl_batch_talk(struct netlink_ctx *ctx, struct list_head *err_list, + break; + + ret = mnl_socket_recvfrom(nl, rcv_buf, sizeof(rcv_buf)); +- if (ret == -1) ++ if (ret == -1) { ++ /* Too many errors, not all errors are displayed. */ ++ if (errno == ENOBUFS) ++ continue; ++ + return -1; ++ } + + /* Continue on error, make sure we get all acknowledgments */ + ret = mnl_cb_run2(rcv_buf, ret, 0, portid, diff --git a/0251-monitor-Quote-device-names-in-chain-declarations-too.patch b/0251-monitor-Quote-device-names-in-chain-declarations-too.patch new file mode 100644 index 0000000..271c8cb --- /dev/null +++ b/0251-monitor-Quote-device-names-in-chain-declarations-too.patch @@ -0,0 +1,38 @@ +From 0d0e194d17046c5f790d4e9f161a8559668b2f95 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:15:07 +0200 +Subject: [PATCH] monitor: Quote device names in chain declarations, too + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit ed1b5b672b2eeb6244bed566227bee2aa7a1e4b4 + +commit ed1b5b672b2eeb6244bed566227bee2aa7a1e4b4 +Author: Phil Sutter +Date: Thu Aug 28 16:47:03 2025 +0200 + + monitor: Quote device names in chain declarations, too + + Fixed commit missed the fact that there are two routines printing chain + declarations. + + Fixes: eb30f236d91a8 ("rule: print chain and flowtable devices in quotes") + Signed-off-by: Phil Sutter + +Signed-off-by: Phil Sutter +--- + src/rule.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/rule.c b/src/rule.c +index 5826f11..5f2fca3 100644 +--- a/src/rule.c ++++ b/src/rule.c +@@ -1126,7 +1126,7 @@ void chain_print_plain(const struct chain *chain, struct output_ctx *octx) + + nft_print(octx, "devices = { "); + for (i = 0; i < chain->dev_array_len; i++) { +- nft_print(octx, "%s", chain->dev_array[i]); ++ nft_print(octx, "\"%s\"", chain->dev_array[i]); + if (i + 1 != chain->dev_array_len) + nft_print(octx, ", "); + } diff --git a/0252-tests-monitor-Fix-regex-collecting-expected-echo-out.patch b/0252-tests-monitor-Fix-regex-collecting-expected-echo-out.patch new file mode 100644 index 0000000..c1bff99 --- /dev/null +++ b/0252-tests-monitor-Fix-regex-collecting-expected-echo-out.patch @@ -0,0 +1,39 @@ +From 802058b24dcaa0e85af63cd4c3df473e5a55ce23 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:15:07 +0200 +Subject: [PATCH] tests: monitor: Fix regex collecting expected echo output + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 419338d96bdb19c10e241387c54f416c551a47c3 + +commit 419338d96bdb19c10e241387c54f416c551a47c3 +Author: Phil Sutter +Date: Wed Sep 3 15:23:12 2025 +0200 + + tests: monitor: Fix regex collecting expected echo output + + No input triggered this bug, but the match would accept "insert" and + "replace" keywords anywhere in the line not just at the beginning as was + intended. + + Fixes: b2506e5504fed ("tests: Merge monitor and echo test suites") + Signed-off-by: Phil Sutter + +Signed-off-by: Phil Sutter +--- + tests/monitor/run-tests.sh | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/tests/monitor/run-tests.sh b/tests/monitor/run-tests.sh +index f1ac790..c6a3322 100755 +--- a/tests/monitor/run-tests.sh ++++ b/tests/monitor/run-tests.sh +@@ -53,7 +53,7 @@ echo_output_append() { + grep '^\(add\|replace\|insert\)' $command_file >>$output_file + return + } +- [[ "$*" =~ ^add|replace|insert ]] && echo "$*" >>$output_file ++ [[ "$*" =~ ^(add|replace|insert) ]] && echo "$*" >>$output_file + } + json_output_filter() { # (filename) + # unify handle values diff --git a/0253-monitor-Recognize-flowtable-add-del-events.patch b/0253-monitor-Recognize-flowtable-add-del-events.patch new file mode 100644 index 0000000..10fef88 --- /dev/null +++ b/0253-monitor-Recognize-flowtable-add-del-events.patch @@ -0,0 +1,260 @@ +From 5f1588ee9832976be1ea3c154bed834c4ad8835e Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:15:07 +0200 +Subject: [PATCH] monitor: Recognize flowtable add/del events + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 73a8adfc2432ec8337288cc90e7c9f4509139846 + +commit 73a8adfc2432ec8337288cc90e7c9f4509139846 +Author: Phil Sutter +Date: Wed May 15 16:01:20 2024 +0200 + + monitor: Recognize flowtable add/del events + + These were entirely ignored before, add the necessary code analogous to + e.g. objects. + + Signed-off-by: Phil Sutter + +Signed-off-by: Phil Sutter +--- + include/json.h | 10 ++++ + include/netlink.h | 1 + + include/rule.h | 1 + + src/json.c | 6 +++ + src/monitor.c | 61 ++++++++++++++++++++++ + src/parser_json.c | 6 +++ + src/rule.c | 15 ++++++ + tests/monitor/testcases/flowtable-simple.t | 10 ++++ + 8 files changed, 110 insertions(+) + create mode 100644 tests/monitor/testcases/flowtable-simple.t + +diff --git a/include/json.h b/include/json.h +index 39be892..0670b87 100644 +--- a/include/json.h ++++ b/include/json.h +@@ -11,6 +11,7 @@ struct nlmsghdr; + struct rule; + struct set; + struct obj; ++struct flowtable; + struct stmt; + struct symbol_table; + struct table; +@@ -113,6 +114,8 @@ void monitor_print_element_json(struct netlink_mon_handler *monh, + const char *cmd, struct set *s); + void monitor_print_obj_json(struct netlink_mon_handler *monh, + const char *cmd, struct obj *o); ++void monitor_print_flowtable_json(struct netlink_mon_handler *monh, ++ const char *cmd, struct flowtable *ft); + void monitor_print_rule_json(struct netlink_mon_handler *monh, + const char *cmd, struct rule *r); + +@@ -254,6 +257,13 @@ static inline void monitor_print_obj_json(struct netlink_mon_handler *monh, + /* empty */ + } + ++static inline void ++monitor_print_flowtable_json(struct netlink_mon_handler *monh, ++ const char *cmd, struct flowtable *ft) ++{ ++ /* empty */ ++} ++ + static inline void monitor_print_rule_json(struct netlink_mon_handler *monh, + const char *cmd, struct rule *r) + { +diff --git a/include/netlink.h b/include/netlink.h +index 80e18ee..60c1a0d 100644 +--- a/include/netlink.h ++++ b/include/netlink.h +@@ -97,6 +97,7 @@ extern struct nftnl_table *netlink_table_alloc(const struct nlmsghdr *nlh); + extern struct nftnl_chain *netlink_chain_alloc(const struct nlmsghdr *nlh); + extern struct nftnl_set *netlink_set_alloc(const struct nlmsghdr *nlh); + extern struct nftnl_obj *netlink_obj_alloc(const struct nlmsghdr *nlh); ++extern struct nftnl_flowtable *netlink_flowtable_alloc(const struct nlmsghdr *nlh); + extern struct nftnl_rule *netlink_rule_alloc(const struct nlmsghdr *nlh); + + struct nft_data_linearize { +diff --git a/include/rule.h b/include/rule.h +index 7f0c1d0..7b6551a 100644 +--- a/include/rule.h ++++ b/include/rule.h +@@ -548,6 +548,7 @@ extern struct flowtable *flowtable_lookup_fuzzy(const char *ft_name, + const struct table **table); + + void flowtable_print(const struct flowtable *n, struct output_ctx *octx); ++void flowtable_print_plain(const struct flowtable *ft, struct output_ctx *octx); + + /** + * enum cmd_ops - command operations +diff --git a/src/json.c b/src/json.c +index 8a56b08..d778801 100644 +--- a/src/json.c ++++ b/src/json.c +@@ -2113,6 +2113,12 @@ void monitor_print_obj_json(struct netlink_mon_handler *monh, + monitor_print_json(monh, cmd, obj_print_json(o)); + } + ++void monitor_print_flowtable_json(struct netlink_mon_handler *monh, ++ const char *cmd, struct flowtable *ft) ++{ ++ monitor_print_json(monh, cmd, flowtable_print_json(ft)); ++} ++ + void monitor_print_rule_json(struct netlink_mon_handler *monh, + const char *cmd, struct rule *r) + { +diff --git a/src/monitor.c b/src/monitor.c +index 6414feb..a446768 100644 +--- a/src/monitor.c ++++ b/src/monitor.c +@@ -127,6 +127,19 @@ struct nftnl_obj *netlink_obj_alloc(const struct nlmsghdr *nlh) + return nlo; + } + ++struct nftnl_flowtable *netlink_flowtable_alloc(const struct nlmsghdr *nlh) ++{ ++ struct nftnl_flowtable *nlf; ++ ++ nlf = nftnl_flowtable_alloc(); ++ if (nlf == NULL) ++ memory_allocation_error(); ++ if (nftnl_flowtable_nlmsg_parse(nlh, nlf) < 0) ++ netlink_abi_error(); ++ ++ return nlf; ++} ++ + static uint32_t netlink_msg2nftnl_of(uint32_t type, uint16_t flags) + { + switch (type) { +@@ -546,6 +559,50 @@ static int netlink_events_obj_cb(const struct nlmsghdr *nlh, int type, + return MNL_CB_OK; + } + ++static int netlink_events_flowtable_cb(const struct nlmsghdr *nlh, int type, ++ struct netlink_mon_handler *monh) ++{ ++ const char *family, *cmd; ++ struct nftnl_flowtable *nlf; ++ struct flowtable *ft; ++ ++ nlf = netlink_flowtable_alloc(nlh); ++ ++ ft = netlink_delinearize_flowtable(monh->ctx, nlf); ++ if (!ft) { ++ nftnl_flowtable_free(nlf); ++ return MNL_CB_ERROR; ++ } ++ family = family2str(ft->handle.family); ++ cmd = netlink_msg2cmd(type, nlh->nlmsg_flags); ++ ++ switch (monh->format) { ++ case NFTNL_OUTPUT_DEFAULT: ++ nft_mon_print(monh, "%s ", cmd); ++ ++ switch (type) { ++ case NFT_MSG_NEWFLOWTABLE: ++ flowtable_print_plain(ft, &monh->ctx->nft->output); ++ break; ++ case NFT_MSG_DELFLOWTABLE: ++ nft_mon_print(monh, "flowtable %s %s %s", family, ++ ft->handle.table.name, ++ ft->handle.flowtable.name); ++ break; ++ } ++ nft_mon_print(monh, "\n"); ++ break; ++ case NFTNL_OUTPUT_JSON: ++ monitor_print_flowtable_json(monh, cmd, ft); ++ if (!nft_output_echo(&monh->ctx->nft->output)) ++ nft_mon_print(monh, "\n"); ++ break; ++ } ++ flowtable_free(ft); ++ nftnl_flowtable_free(nlf); ++ return MNL_CB_OK; ++} ++ + static void rule_map_decompose_cb(struct set *s, void *data) + { + if (!set_is_anonymous(s->flags)) +@@ -966,6 +1023,10 @@ static int netlink_events_cb(const struct nlmsghdr *nlh, void *data) + case NFT_MSG_DELOBJ: + ret = netlink_events_obj_cb(nlh, type, monh); + break; ++ case NFT_MSG_NEWFLOWTABLE: ++ case NFT_MSG_DELFLOWTABLE: ++ ret = netlink_events_flowtable_cb(nlh, type, monh); ++ break; + case NFT_MSG_NEWGEN: + ret = netlink_events_newgen_cb(nlh, type, monh); + break; +diff --git a/src/parser_json.c b/src/parser_json.c +index 6cefb39..9cae2d7 100644 +--- a/src/parser_json.c ++++ b/src/parser_json.c +@@ -4269,6 +4269,7 @@ static int json_echo_error(struct netlink_mon_handler *monh, + + static uint64_t handle_from_nlmsg(const struct nlmsghdr *nlh) + { ++ struct nftnl_flowtable *nlf; + struct nftnl_table *nlt; + struct nftnl_chain *nlc; + struct nftnl_rule *nlr; +@@ -4305,6 +4306,11 @@ static uint64_t handle_from_nlmsg(const struct nlmsghdr *nlh) + handle = nftnl_obj_get_u64(nlo, NFTNL_OBJ_HANDLE); + nftnl_obj_free(nlo); + break; ++ case NFT_MSG_NEWFLOWTABLE: ++ nlf = netlink_flowtable_alloc(nlh); ++ handle = nftnl_flowtable_get_u64(nlf, NFTNL_FLOWTABLE_HANDLE); ++ nftnl_flowtable_free(nlf); ++ break; + } + return handle; + } +diff --git a/src/rule.c b/src/rule.c +index 5f2fca3..f64cd68 100644 +--- a/src/rule.c ++++ b/src/rule.c +@@ -2173,6 +2173,21 @@ void flowtable_print(const struct flowtable *s, struct output_ctx *octx) + do_flowtable_print(s, &opts, octx); + } + ++void flowtable_print_plain(const struct flowtable *ft, struct output_ctx *octx) ++{ ++ struct print_fmt_options opts = { ++ .tab = "", ++ .nl = " ", ++ .table = ft->handle.table.name, ++ .family = family2str(ft->handle.family), ++ .stmt_separator = "; ", ++ }; ++ ++ flowtable_print_declaration(ft, &opts, octx); ++ nft_print(octx, "}"); ++} ++ ++ + struct flowtable *flowtable_lookup_fuzzy(const char *ft_name, + const struct nft_cache *cache, + const struct table **t) +diff --git a/tests/monitor/testcases/flowtable-simple.t b/tests/monitor/testcases/flowtable-simple.t +new file mode 100644 +index 0000000..df8eccb +--- /dev/null ++++ b/tests/monitor/testcases/flowtable-simple.t +@@ -0,0 +1,10 @@ ++# setup first ++I add table ip t ++I add flowtable ip t ft { hook ingress priority 0; devices = { lo }; } ++O - ++J {"add": {"table": {"family": "ip", "name": "t", "handle": 0}}} ++J {"add": {"flowtable": {"family": "ip", "name": "ft", "table": "t", "handle": 0, "hook": "ingress", "prio": 0, "dev": "lo"}}} ++ ++I delete flowtable ip t ft ++O - ++J {"delete": {"flowtable": {"family": "ip", "name": "ft", "table": "t", "handle": 0, "hook": "ingress", "prio": 0, "dev": "lo"}}} diff --git a/0254-monitor-Inform-JSON-printer-when-reporting-an-object.patch b/0254-monitor-Inform-JSON-printer-when-reporting-an-object.patch new file mode 100644 index 0000000..adf4f27 --- /dev/null +++ b/0254-monitor-Inform-JSON-printer-when-reporting-an-object.patch @@ -0,0 +1,178 @@ +From bcd85ab569ce349e3d96550607d6919efaae9d02 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:15:07 +0200 +Subject: [PATCH] monitor: Inform JSON printer when reporting an object delete + event + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 6c04d24d16f1d15f216f2b3c8e64c9062cd77487 +Conflicts: Adjusted to missing commit 3a957f8f1ff1e + ("tunnel: add tunnel object and statement json support") + +commit 6c04d24d16f1d15f216f2b3c8e64c9062cd77487 +Author: Phil Sutter +Date: Fri Aug 29 01:07:05 2025 +0200 + + monitor: Inform JSON printer when reporting an object delete event + + Since kernel commit a1050dd07168 ("netfilter: nf_tables: Reintroduce + shortened deletion notifications"), type-specific data is no longer + dumped when notifying for a deleted object. JSON output was not aware of + this and tried to print bogus data. + + Fixes: 9e88aae28e9f4 ("monitor: Use libnftables JSON output") + Signed-off-by: Phil Sutter + Reviewed-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + include/json.h | 5 +++-- + src/json.c | 15 ++++++++++----- + src/monitor.c | 2 +- + tests/monitor/testcases/object.t | 10 +++++----- + 4 files changed, 19 insertions(+), 13 deletions(-) + +diff --git a/include/json.h b/include/json.h +index 0670b87..70f8f72 100644 +--- a/include/json.h ++++ b/include/json.h +@@ -113,7 +113,7 @@ void monitor_print_set_json(struct netlink_mon_handler *monh, + void monitor_print_element_json(struct netlink_mon_handler *monh, + const char *cmd, struct set *s); + void monitor_print_obj_json(struct netlink_mon_handler *monh, +- const char *cmd, struct obj *o); ++ const char *cmd, struct obj *o, bool delete); + void monitor_print_flowtable_json(struct netlink_mon_handler *monh, + const char *cmd, struct flowtable *ft); + void monitor_print_rule_json(struct netlink_mon_handler *monh, +@@ -252,7 +252,8 @@ static inline void monitor_print_element_json(struct netlink_mon_handler *monh, + } + + static inline void monitor_print_obj_json(struct netlink_mon_handler *monh, +- const char *cmd, struct obj *o) ++ const char *cmd, struct obj *o, ++ bool delete) + { + /* empty */ + } +diff --git a/src/json.c b/src/json.c +index d778801..340c2cc 100644 +--- a/src/json.c ++++ b/src/json.c +@@ -364,7 +364,8 @@ static json_t *timeout_policy_json(uint8_t l4, const uint32_t *timeout) + return root ? : json_null(); + } + +-static json_t *obj_print_json(const struct obj *obj) ++static json_t *obj_print_json(const struct obj *obj, ++ bool delete) + { + const char *rate_unit = NULL, *burst_unit = NULL; + const char *type = obj_type_name(obj->type); +@@ -377,6 +378,9 @@ static json_t *obj_print_json(const struct obj *obj) + "table", obj->handle.table.name, + "handle", obj->handle.handle.id); + ++ if (delete) ++ goto out; ++ + if (obj->comment) { + tmp = nft_json_pack("{s:s}", "comment", obj->comment); + json_object_update(root, tmp); +@@ -480,6 +484,7 @@ static json_t *obj_print_json(const struct obj *obj) + break; + } + ++out: + return nft_json_pack("{s:o}", type, root); + } + +@@ -1722,7 +1727,7 @@ static json_t *table_print_json_full(struct netlink_ctx *ctx, + json_array_append_new(root, tmp); + + list_for_each_entry(obj, &table->obj_cache.list, cache.list) { +- tmp = obj_print_json(obj); ++ tmp = obj_print_json(obj, false); + json_array_append_new(root, tmp); + } + list_for_each_entry(set, &table->set_cache.list, cache.list) { +@@ -1900,7 +1905,7 @@ static json_t *do_list_obj_json(struct netlink_ctx *ctx, + strcmp(cmd->handle.obj.name, obj->handle.obj.name))) + continue; + +- json_array_append_new(root, obj_print_json(obj)); ++ json_array_append_new(root, obj_print_json(obj, false)); + } + } + +@@ -2108,9 +2113,9 @@ void monitor_print_element_json(struct netlink_mon_handler *monh, + } + + void monitor_print_obj_json(struct netlink_mon_handler *monh, +- const char *cmd, struct obj *o) ++ const char *cmd, struct obj *o, bool delete) + { +- monitor_print_json(monh, cmd, obj_print_json(o)); ++ monitor_print_json(monh, cmd, obj_print_json(o, delete)); + } + + void monitor_print_flowtable_json(struct netlink_mon_handler *monh, +diff --git a/src/monitor.c b/src/monitor.c +index a446768..b9b0263 100644 +--- a/src/monitor.c ++++ b/src/monitor.c +@@ -549,7 +549,7 @@ static int netlink_events_obj_cb(const struct nlmsghdr *nlh, int type, + nft_mon_print(monh, "\n"); + break; + case NFTNL_OUTPUT_JSON: +- monitor_print_obj_json(monh, cmd, obj); ++ monitor_print_obj_json(monh, cmd, obj, type == NFT_MSG_DELOBJ); + if (!nft_output_echo(&monh->ctx->nft->output)) + nft_mon_print(monh, "\n"); + break; +diff --git a/tests/monitor/testcases/object.t b/tests/monitor/testcases/object.t +index 53a9f8c..b60dc98 100644 +--- a/tests/monitor/testcases/object.t ++++ b/tests/monitor/testcases/object.t +@@ -9,7 +9,7 @@ J {"add": {"counter": {"family": "ip", "name": "c", "table": "t", "handle": 0, " + + I delete counter ip t c + O - +-J {"delete": {"counter": {"family": "ip", "name": "c", "table": "t", "handle": 0, "packets": 0, "bytes": 0}}} ++J {"delete": {"counter": {"family": "ip", "name": "c", "table": "t", "handle": 0}}} + + # FIXME: input/output shouldn't be asynchronous here + I add quota ip t q 25 mbytes +@@ -18,7 +18,7 @@ J {"add": {"quota": {"family": "ip", "name": "q", "table": "t", "handle": 0, "by + + I delete quota ip t q + O - +-J {"delete": {"quota": {"family": "ip", "name": "q", "table": "t", "handle": 0, "bytes": 26214400, "used": 0, "inv": false}}} ++J {"delete": {"quota": {"family": "ip", "name": "q", "table": "t", "handle": 0}}} + + # FIXME: input/output shouldn't be asynchronous here + I add limit ip t l rate 1/second +@@ -27,7 +27,7 @@ J {"add": {"limit": {"family": "ip", "name": "l", "table": "t", "handle": 0, "ra + + I delete limit ip t l + O - +-J {"delete": {"limit": {"family": "ip", "name": "l", "table": "t", "handle": 0, "rate": 1, "per": "second", "burst": 5}}} ++J {"delete": {"limit": {"family": "ip", "name": "l", "table": "t", "handle": 0}}} + + I add ct helper ip t cth { type "sip" protocol tcp; l3proto ip; } + O - +@@ -35,7 +35,7 @@ J {"add": {"ct helper": {"family": "ip", "name": "cth", "table": "t", "handle": + + I delete ct helper ip t cth + O - +-J {"delete": {"ct helper": {"family": "ip", "name": "cth", "table": "t", "handle": 0, "type": "sip", "protocol": "tcp", "l3proto": "ip"}}} ++J {"delete": {"ct helper": {"family": "ip", "name": "cth", "table": "t", "handle": 0}}} + + I add ct timeout ip t ctt { protocol udp; l3proto ip; policy = { unreplied : 15s, replied : 12s }; } + O - +@@ -43,4 +43,4 @@ J {"add": {"ct timeout": {"family": "ip", "name": "ctt", "table": "t", "handle": + + I delete ct timeout ip t ctt + O - +-J {"delete": {"ct timeout": {"family": "ip", "name": "ctt", "table": "t", "handle": 0, "protocol": "udp", "l3proto": "ip", "policy": {"unreplied": 15, "replied": 12}}}} ++J {"delete": {"ct timeout": {"family": "ip", "name": "ctt", "table": "t", "handle": 0}}} diff --git a/0255-tests-Prepare-exit-codes-for-automake.patch b/0255-tests-Prepare-exit-codes-for-automake.patch new file mode 100644 index 0000000..38c3ad7 --- /dev/null +++ b/0255-tests-Prepare-exit-codes-for-automake.patch @@ -0,0 +1,112 @@ +From 98660a0a6450d9dac867d82f0f59f171d8c08b76 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:15:24 +0200 +Subject: [PATCH] tests: Prepare exit codes for automake + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 15c01d744a6b8f428e785a36687e7ecdbc3e7a15 + +commit 15c01d744a6b8f428e785a36687e7ecdbc3e7a15 +Author: Phil Sutter +Date: Thu Aug 31 12:44:55 2023 +0200 + + tests: Prepare exit codes for automake + + Make the test suite runners exit 77 when requiring root and running as + regular user, exit 99 for internal errors (unrelated to test cases) and + exit 1 (or any free non-zero value) to indicate test failures. + + Signed-off-by: Phil Sutter + +Signed-off-by: Phil Sutter +--- + tests/monitor/run-tests.sh | 11 ++++------- + tests/py/nft-test.py | 12 +++++++----- + tests/shell/run-tests.sh | 2 +- + 3 files changed, 12 insertions(+), 13 deletions(-) + +diff --git a/tests/monitor/run-tests.sh b/tests/monitor/run-tests.sh +index c6a3322..7cbc7b1 100755 +--- a/tests/monitor/run-tests.sh ++++ b/tests/monitor/run-tests.sh +@@ -13,18 +13,15 @@ err() { + echo "$*" >&2 + } + +-die() { +- err "$*" +- exit 1 +-} +- + if [ "$(id -u)" != "0" ] ; then +- die "this requires root!" ++ err "this requires root!" ++ exit 77 + fi + + testdir=$(mktemp -d) + if [ ! -d $testdir ]; then +- die "Failed to create test directory" ++ err "Failed to create test directory" ++ exit 99 + fi + trap 'rm -rf $testdir; $nft flush ruleset' EXIT + +diff --git a/tests/py/nft-test.py b/tests/py/nft-test.py +index 4799636..5ce9a64 100755 +--- a/tests/py/nft-test.py ++++ b/tests/py/nft-test.py +@@ -1513,7 +1513,7 @@ def main(): + + if os.getuid() != 0: + print("You need to be root to run this, sorry") +- return ++ return 77 + + if not args.no_netns and not spawn_netns(): + print_warning("cannot run in own namespace, connectivity might break") +@@ -1532,11 +1532,11 @@ def main(): + if check_lib_path and not os.path.exists(args.library): + print("The nftables library at '%s' does not exist. " + "You need to build the project." % args.library) +- return ++ return 99 + + if args.enable_schema and not args.enable_json: + print_error("Option --schema requires option --json") +- return ++ return 99 + + global nftables + nftables = Nftables(sofile = args.library) +@@ -1549,7 +1549,7 @@ def main(): + print_info("Log will be available at %s" % LOGFILE) + except IOError: + print_error("Cannot open log file %s" % LOGFILE) +- return ++ return 99 + + file_list = [] + if args.filenames: +@@ -1595,5 +1595,7 @@ def main(): + print("%d test files, %d files passed, %d unit tests, " % (test_files, files_ok, tests)) + print("%d error, %d warning" % (errors, warnings)) + ++ return errors != 0 ++ + if __name__ == '__main__': +- main() ++ sys.exit(main()) +diff --git a/tests/shell/run-tests.sh b/tests/shell/run-tests.sh +index 22105c2..beb7dda 100755 +--- a/tests/shell/run-tests.sh ++++ b/tests/shell/run-tests.sh +@@ -79,7 +79,7 @@ _msg() { + printf '%s\n' "$level: $*" + fi + if [ "$level" = E ] ; then +- exit 1 ++ exit 99 + fi + } + diff --git a/0256-fib-Fix-for-existence-check-on-Big-Endian.patch b/0256-fib-Fix-for-existence-check-on-Big-Endian.patch new file mode 100644 index 0000000..eae1ab8 --- /dev/null +++ b/0256-fib-Fix-for-existence-check-on-Big-Endian.patch @@ -0,0 +1,67 @@ +From 4c116af60aac07ebd1335066844b08070ac140b4 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:15:24 +0200 +Subject: [PATCH] fib: Fix for existence check on Big Endian + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 98e51e687616a4b54efa3b723917c292e3acc380 + +commit 98e51e687616a4b54efa3b723917c292e3acc380 +Author: Phil Sutter +Date: Tue Sep 9 22:27:19 2025 +0200 + + fib: Fix for existence check on Big Endian + + Adjust the expression size to 1B so cmp expression value is correct. + Without this, the rule 'fib saddr . iif check exists' generates + following byte code on BE: + + | [ fib saddr . iif oif present => reg 1 ] + | [ cmp eq reg 1 0x00000001 ] + + Though with NFTA_FIB_F_PRESENT flag set, nft_fib.ko writes to the first + byte of reg 1 only (using nft_reg_store8()). With this patch in place, + byte code is correct: + + | [ fib saddr . iif oif present => reg 1 ] + | [ cmp eq reg 1 0x01000000 ] + + Fixes: f686a17eafa0b ("fib: Support existence check") + Cc: Yi Chen + Signed-off-by: Phil Sutter + Reviewed-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/evaluate.c | 1 + + src/fib.c | 4 +++- + 2 files changed, 4 insertions(+), 1 deletion(-) + +diff --git a/src/evaluate.c b/src/evaluate.c +index 93f0bbf..fa3122a 100644 +--- a/src/evaluate.c ++++ b/src/evaluate.c +@@ -2702,6 +2702,7 @@ static int expr_evaluate_fib(struct eval_ctx *ctx, struct expr **exprp) + if (expr->flags & EXPR_F_BOOLEAN) { + expr->fib.flags |= NFTA_FIB_F_PRESENT; + datatype_set(expr, &boolean_type); ++ expr->len = BITS_PER_BYTE; + } + return expr_evaluate_primary(ctx, exprp); + } +diff --git a/src/fib.c b/src/fib.c +index 5383613..4db7cd2 100644 +--- a/src/fib.c ++++ b/src/fib.c +@@ -198,8 +198,10 @@ struct expr *fib_expr_alloc(const struct location *loc, + BUG("Unknown result %d\n", result); + } + +- if (flags & NFTA_FIB_F_PRESENT) ++ if (flags & NFTA_FIB_F_PRESENT) { + type = &boolean_type; ++ len = BITS_PER_BYTE; ++ } + + expr = expr_alloc(loc, EXPR_FIB, type, + BYTEORDER_HOST_ENDIAN, len); diff --git a/0257-tests-py-objects.t-must-use-input-not-output.patch b/0257-tests-py-objects.t-must-use-input-not-output.patch new file mode 100644 index 0000000..18dd035 --- /dev/null +++ b/0257-tests-py-objects.t-must-use-input-not-output.patch @@ -0,0 +1,137 @@ +From 1a6bfca19bc7ef2e6fc47585ce081e5ae83e17c5 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:15:24 +0200 +Subject: [PATCH] tests: py: objects.t: must use input, not output + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 6b71d114912cd269416f232c0f0ec2fcc42eb58f + +commit 6b71d114912cd269416f232c0f0ec2fcc42eb58f +Author: Florian Westphal +Date: Tue Oct 7 12:48:49 2025 +0200 + + tests: py: objects.t: must use input, not output + + synproxy must never be used in output rules, doing so results in kernel + crash due to infinite recursive calls back to nf_hook_slow() for the + emitted reply packet. + + Up until recently kernel lacked this validation, and now that the kernel + rejects this the test fails. Use input to make this pass again. + + A new test to ensure we reject synproxy in ouput should be added + in the near future. + + Signed-off-by: Florian Westphal + Reviewed-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + tests/py/ip/objects.t | 4 ++-- + tests/py/ip/objects.t.payload | 22 +++++++++++----------- + 2 files changed, 13 insertions(+), 13 deletions(-) + +diff --git a/tests/py/ip/objects.t b/tests/py/ip/objects.t +index 4fcde7c..71d5ffe 100644 +--- a/tests/py/ip/objects.t ++++ b/tests/py/ip/objects.t +@@ -1,6 +1,6 @@ +-:output;type filter hook output priority 0 ++:input;type filter hook input priority 0 + +-*ip;test-ip4;output ++*ip;test-ip4;input + + # counter + %cnt1 type counter;ok +diff --git a/tests/py/ip/objects.t.payload b/tests/py/ip/objects.t.payload +index 5252724..3da4b28 100644 +--- a/tests/py/ip/objects.t.payload ++++ b/tests/py/ip/objects.t.payload +@@ -1,5 +1,5 @@ + # ip saddr 192.168.1.3 counter name "cnt2" +-ip test-ip4 output ++ip test-ip4 input + [ payload load 4b @ network header + 12 => reg 1 ] + [ cmp eq reg 1 0x0301a8c0 ] + [ objref type 1 name cnt2 ] +@@ -8,14 +8,14 @@ ip test-ip4 output + __objmap%d test-ip4 43 + __objmap%d test-ip4 0 + element 0000bb01 : 0 [end] element 00005000 : 0 [end] element 00001600 : 0 [end] +-ip test-ip4 output ++ip test-ip4 input + [ meta load l4proto => reg 1 ] + [ cmp eq reg 1 0x00000006 ] + [ payload load 2b @ transport header + 2 => reg 1 ] + [ objref sreg 1 set __objmap%d ] + + # ip saddr 192.168.1.3 quota name "qt1" +-ip test-ip4 output ++ip test-ip4 input + [ payload load 4b @ network header + 12 => reg 1 ] + [ cmp eq reg 1 0x0301a8c0 ] + [ objref type 2 name qt1 ] +@@ -24,28 +24,28 @@ ip test-ip4 output + __objmap%d test-ip4 43 + __objmap%d test-ip4 0 + element 0000bb01 : 0 [end] element 00005000 : 0 [end] element 00001600 : 0 [end] +-ip test-ip4 output ++ip test-ip4 input + [ meta load l4proto => reg 1 ] + [ cmp eq reg 1 0x00000006 ] + [ payload load 2b @ transport header + 2 => reg 1 ] + [ objref sreg 1 set __objmap%d ] + + # ct helper set "cthelp1" +-ip test-ip4 output ++ip test-ip4 input + [ objref type 3 name cthelp1 ] + + # ct helper set tcp dport map {21 : "cthelp1", 2121 : "cthelp1" } + __objmap%d test-ip4 43 + __objmap%d test-ip4 0 + element 00001500 : 0 [end] element 00004908 : 0 [end] +-ip test-ip4 output ++ip test-ip4 input + [ meta load l4proto => reg 1 ] + [ cmp eq reg 1 0x00000006 ] + [ payload load 2b @ transport header + 2 => reg 1 ] + [ objref sreg 1 set __objmap%d ] + + # ip saddr 192.168.1.3 limit name "lim1" +-ip test-ip4 output ++ip test-ip4 input + [ payload load 4b @ network header + 12 => reg 1 ] + [ cmp eq reg 1 0x0301a8c0 ] + [ objref type 4 name lim1 ] +@@ -54,25 +54,25 @@ ip test-ip4 output + __objmap%d test-ip4 43 size 3 + __objmap%d test-ip4 0 + element 0000bb01 : 0 [end] element 00005000 : 0 [end] element 00001600 : 0 [end] +-ip test-ip4 output ++ip test-ip4 input + [ meta load l4proto => reg 1 ] + [ cmp eq reg 1 0x00000006 ] + [ payload load 2b @ transport header + 2 => reg 1 ] + [ objref sreg 1 set __objmap%d ] + + # ct timeout set "cttime1" +-ip test-ip4 output ++ip test-ip4 input + [ objref type 7 name cttime1 ] + + # ct expectation set "ctexpect1" +-ip test-ip4 output ++ip test-ip4 input + [ objref type 9 name ctexpect1 ] + + # synproxy name tcp dport map {443 : "synproxy1", 80 : "synproxy2"} + __objmap%d test-ip4 43 size 2 + __objmap%d test-ip4 0 + element 0000bb01 : 0 [end] element 00005000 : 0 [end] +-ip test-ip4 output ++ip test-ip4 input + [ meta load l4proto => reg 1 ] + [ cmp eq reg 1 0x00000006 ] + [ payload load 2b @ transport header + 2 => reg 1 ] diff --git a/0258-doc-fix-tcpdump-example.patch b/0258-doc-fix-tcpdump-example.patch new file mode 100644 index 0000000..dec552c --- /dev/null +++ b/0258-doc-fix-tcpdump-example.patch @@ -0,0 +1,41 @@ +From d1311c23e56c62effabdbbe09d2427d268231248 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:15:24 +0200 +Subject: [PATCH] doc: fix tcpdump example + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit b9516b0a4dfb6e16e3e11c3024683a2df1ea09ab + +commit b9516b0a4dfb6e16e3e11c3024683a2df1ea09ab +Author: Georg Pfuetzenreuter +Date: Mon Oct 13 19:17:31 2025 +0200 + + doc: fix tcpdump example + + The expression needs to be enclosed in a single string and combined with + a logical AND to have the desired effect. + + Fixes: 1188a69604c3 ("src: introduce SYNPROXY matching") + Signed-off-by: Georg Pfuetzenreuter + Reviewed-by: Fernando Fernandez Mancera + Signed-off-by: Florian Westphal + +Signed-off-by: Phil Sutter +--- + doc/statements.txt | 3 +-- + 1 file changed, 1 insertion(+), 2 deletions(-) + +diff --git a/doc/statements.txt b/doc/statements.txt +index c4a2aff..2de3ab2 100644 +--- a/doc/statements.txt ++++ b/doc/statements.txt +@@ -632,8 +632,7 @@ needed for selective acknowledgement and window scaling). + --------------------------------------- + Determine tcp options used by backend, from an external system + +- tcpdump -pni eth0 -c 1 'tcp[tcpflags] == (tcp-syn|tcp-ack)' +- port 80 & ++ tcpdump -pni eth0 -c 1 'tcp[tcpflags] == (tcp-syn|tcp-ack) && port 80' & + telnet 192.0.2.42 80 + 18:57:24.693307 IP 192.0.2.42.80 > 192.0.2.43.48757: + Flags [S.], seq 360414582, ack 788841994, win 14480, diff --git a/0259-src-parser_json-fix-format-string-bugs.patch b/0259-src-parser_json-fix-format-string-bugs.patch new file mode 100644 index 0000000..75883ee --- /dev/null +++ b/0259-src-parser_json-fix-format-string-bugs.patch @@ -0,0 +1,60 @@ +From daf40b85f2c6cc6c42a217661e68d2387e2fc965 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:15:24 +0200 +Subject: [PATCH] src: parser_json: fix format string bugs + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit b30ad0c25b7b4a289dd73e8da6fa101b66fbf0d7 + +commit b30ad0c25b7b4a289dd73e8da6fa101b66fbf0d7 +Author: Florian Westphal +Date: Thu Oct 23 14:17:00 2025 +0200 + + src: parser_json: fix format string bugs + + After adding fmt attribute annotation: + warning: format not a string literal and no format arguments [-Wformat-security] + 131 | erec_queue(error(&loc, err->text), ctx->msgs); + In function 'json_events_cb': + warning: format '%lu' expects argument of type 'long unsigned int', but argument 3 has type '__u32' {aka 'unsigned int'} [-Wformat=] + + Fix that up too. + + Fixes: 586ad210368b ("libnftables: Implement JSON parser") + Signed-off-by: Florian Westphal + +Signed-off-by: Phil Sutter +--- + src/parser_json.c | 5 +++-- + 1 file changed, 3 insertions(+), 2 deletions(-) + +diff --git a/src/parser_json.c b/src/parser_json.c +index 9cae2d7..61427ca 100644 +--- a/src/parser_json.c ++++ b/src/parser_json.c +@@ -129,7 +129,7 @@ static void json_lib_error(struct json_ctx *ctx, json_error_t *err) + .last_column = err->column, + }; + +- erec_queue(error(&loc, err->text), ctx->msgs); ++ erec_queue(error(&loc, "%s", err->text), ctx->msgs); + } + + __attribute__((format(printf, 2, 3))) +@@ -4253,6 +4253,7 @@ int nft_parse_json_filename(struct nft_ctx *nft, const char *filename, + return ret; + } + ++__attribute__((format(printf, 2, 3))) + static int json_echo_error(struct netlink_mon_handler *monh, + const char *fmt, ...) + { +@@ -4325,7 +4326,7 @@ int json_events_cb(const struct nlmsghdr *nlh, struct netlink_mon_handler *monh) + + json = seqnum_to_json(nlh->nlmsg_seq); + if (!json) { +- json_echo_error(monh, "No JSON command found with seqnum %lu\n", ++ json_echo_error(monh, "No JSON command found with seqnum %u\n", + nlh->nlmsg_seq); + return MNL_CB_OK; + } diff --git a/0260-optimize-Fix-verdict-expression-comparison.patch b/0260-optimize-Fix-verdict-expression-comparison.patch new file mode 100644 index 0000000..b3de87e --- /dev/null +++ b/0260-optimize-Fix-verdict-expression-comparison.patch @@ -0,0 +1,56 @@ +From e47e6b806a16a681d4185bb6a35dcc4c7696bb7a Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:15:24 +0200 +Subject: [PATCH] optimize: Fix verdict expression comparison + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 695ee5a8b174f86e2e64786530147e56d8d27f19 + +commit 695ee5a8b174f86e2e64786530147e56d8d27f19 +Author: Phil Sutter +Date: Wed Oct 22 14:03:37 2025 +0200 + + optimize: Fix verdict expression comparison + + In verdict expression, 'chain' points at a constant expression of + verdict_type, not a symbol expression. Therefore 'chain->identifier' + points eight bytes (on 64bit systems) into the mpz_t 'value' holding the + chain name. This matches the '_mp_d' data pointer, so works by accident. + + Fix this by copying what verdict_jump_chain_print() does and export + chain names before comparing. + + Fixes: fb298877ece27 ("src: add ruleset optimization infrastructure") + Signed-off-by: Phil Sutter + +Signed-off-by: Phil Sutter +--- + src/optimize.c | 11 ++++++++--- + 1 file changed, 8 insertions(+), 3 deletions(-) + +diff --git a/src/optimize.c b/src/optimize.c +index c386e61..a0cb307 100644 +--- a/src/optimize.c ++++ b/src/optimize.c +@@ -337,13 +337,18 @@ static bool __stmt_type_eq(const struct stmt *stmt_a, const struct stmt *stmt_b, + + static bool expr_verdict_eq(const struct expr *expr_a, const struct expr *expr_b) + { ++ char chain_a[NFT_CHAIN_MAXNAMELEN]; ++ char chain_b[NFT_CHAIN_MAXNAMELEN]; ++ + if (expr_a->verdict != expr_b->verdict) + return false; + if (expr_a->chain && expr_b->chain) { +- if (expr_a->chain->etype != expr_b->chain->etype) ++ if (expr_a->chain->etype != EXPR_VALUE || ++ expr_a->chain->etype != expr_b->chain->etype) + return false; +- if (expr_a->chain->etype == EXPR_VALUE && +- strcmp(expr_a->chain->identifier, expr_b->chain->identifier)) ++ expr_chain_export(expr_a->chain, chain_a); ++ expr_chain_export(expr_b->chain, chain_b); ++ if (strcmp(chain_a, chain_b)) + return false; + } else if (expr_a->chain || expr_b->chain) { + return false; diff --git a/0261-datatype-Fix-boolean-type-on-Big-Endian.patch b/0261-datatype-Fix-boolean-type-on-Big-Endian.patch new file mode 100644 index 0000000..e3ac5f8 --- /dev/null +++ b/0261-datatype-Fix-boolean-type-on-Big-Endian.patch @@ -0,0 +1,39 @@ +From 85d6ddc8aab26c4a86c74513d548f6443f1e7e9d Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:15:24 +0200 +Subject: [PATCH] datatype: Fix boolean type on Big Endian + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit aec699af2a006b1dd6580abb95910c402e306fa9 + +commit aec699af2a006b1dd6580abb95910c402e306fa9 +Author: Phil Sutter +Date: Wed Oct 8 23:19:08 2025 +0200 + + datatype: Fix boolean type on Big Endian + + Pass a reference to a variable with correct size when creating the + expression, otherwise mpz_import_data() will read only the always zero + upper byte on Big Endian hosts. + + Fixes: afb6a8e66a111 ("datatype: clamp boolean value to 0 and 1") + Signed-off-by: Phil Sutter + +Signed-off-by: Phil Sutter +--- + src/datatype.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/datatype.c b/src/datatype.c +index 6f73dd3..1e7e12d 100644 +--- a/src/datatype.c ++++ b/src/datatype.c +@@ -1485,7 +1485,7 @@ static struct error_record *boolean_type_parse(struct parse_ctx *ctx, + struct expr **res) + { + struct error_record *erec; +- int num; ++ uint8_t num; + + erec = integer_type_parse(ctx, sym, res); + if (erec) diff --git a/0262-tests-py-any-ct.t.json.output-Drop-leftover-entry.patch b/0262-tests-py-any-ct.t.json.output-Drop-leftover-entry.patch new file mode 100644 index 0000000..570e1e6 --- /dev/null +++ b/0262-tests-py-any-ct.t.json.output-Drop-leftover-entry.patch @@ -0,0 +1,69 @@ +From 3776d5026b60b0bb0bf614048032038286b96822 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:15:25 +0200 +Subject: [PATCH] tests: py: any/ct.t.json.output: Drop leftover entry + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 1801480314bf2baa6abf9cb15ccdef975b966867 + +commit 1801480314bf2baa6abf9cb15ccdef975b966867 +Author: Phil Sutter +Date: Thu Oct 16 16:23:12 2025 +0200 + + tests: py: any/ct.t.json.output: Drop leftover entry + + The rule with single element anonymous set was replaced, drop this + leftover. + + Fixes: 27f6a4c68b4fd ("tests: replace single element sets") + Signed-off-by: Phil Sutter + +Signed-off-by: Phil Sutter +--- + tests/py/any/ct.t.json.output | 33 --------------------------------- + 1 file changed, 33 deletions(-) + +diff --git a/tests/py/any/ct.t.json.output b/tests/py/any/ct.t.json.output +index 70ade7e..82634c2 100644 +--- a/tests/py/any/ct.t.json.output ++++ b/tests/py/any/ct.t.json.output +@@ -471,39 +471,6 @@ + } + ] + +-# ct state . ct mark { new . 0x12345678} +-[ +- { +- "match": { +- "left": { +- "concat": [ +- { +- "ct": { +- "key": "state" +- } +- }, +- { +- "ct": { +- "key": "mark" +- } +- } +- ] +- }, +- "op": "==", +- "right": { +- "set": [ +- { +- "concat": [ +- "new", +- 305419896 +- ] +- } +- ] +- } +- } +- } +-] +- + # ct state . ct mark { new . 0x12345678, new . 0x34127856, established . 0x12785634} + [ + { diff --git a/0263-tests-py-Fix-for-using-wrong-payload-path.patch b/0263-tests-py-Fix-for-using-wrong-payload-path.patch new file mode 100644 index 0000000..8b8fe58 --- /dev/null +++ b/0263-tests-py-Fix-for-using-wrong-payload-path.patch @@ -0,0 +1,58 @@ +From e2a23be07644c26335d2842aa77b76067eb968ba Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:15:25 +0200 +Subject: [PATCH] tests: py: Fix for using wrong payload path + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit aad26d166278aacbd42ce0d1f258c6fd30a05c7c + +commit aad26d166278aacbd42ce0d1f258c6fd30a05c7c +Author: Phil Sutter +Date: Thu Oct 2 13:22:56 2025 +0200 + + tests: py: Fix for using wrong payload path + + If one family has a per-family payload record, following families used + it by accident for a .got file when they actually should use the generic + name. + + Signed-off-by: Phil Sutter + +Signed-off-by: Phil Sutter +--- + tests/py/nft-test.py | 7 ++++--- + 1 file changed, 4 insertions(+), 3 deletions(-) + +diff --git a/tests/py/nft-test.py b/tests/py/nft-test.py +index 5ce9a64..6eddc8b 100755 +--- a/tests/py/nft-test.py ++++ b/tests/py/nft-test.py +@@ -815,9 +815,10 @@ def rule_add(rule, filename, lineno, force_all_family_option, filename_path): + for table in table_list: + if rule[1].strip() == "ok": + table_payload_expected = None ++ table_payload_path = payload_path + try: + payload_log = open("%s.payload.%s" % (filename_path, table.family)) +- payload_path = payload_log.name ++ table_payload_path = payload_log.name + table_payload_expected = payload_find_expected(payload_log, rule[0]) + except: + if not payload_log: +@@ -866,14 +867,14 @@ def rule_add(rule, filename, lineno, force_all_family_option, filename_path): + error += 1 + + try: +- gotf = open("%s.got" % payload_path) ++ gotf = open("%s.got" % table_payload_path) + gotf_payload_expected = payload_find_expected(gotf, rule[0]) + gotf.close() + except: + gotf_payload_expected = None + payload_log.seek(0, 0) + if not payload_check(gotf_payload_expected, payload_log, cmd): +- gotf = open("%s.got" % payload_path, 'a') ++ gotf = open("%s.got" % table_payload_path, 'a') + payload_log.seek(0, 0) + gotf.write("# %s\n" % rule[0]) + while True: diff --git a/0264-tests-py-Do-not-rely-upon-end-marker.patch b/0264-tests-py-Do-not-rely-upon-end-marker.patch new file mode 100644 index 0000000..37ea0b6 --- /dev/null +++ b/0264-tests-py-Do-not-rely-upon-end-marker.patch @@ -0,0 +1,79 @@ +From d6035c820bb1eba9e133f0ce78baa767f1029b01 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:15:25 +0200 +Subject: [PATCH] tests: py: Do not rely upon '[end]' marker + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit ae052a6565f9e9a62ba68d0cbdd1993015310100 + +commit ae052a6565f9e9a62ba68d0cbdd1993015310100 +Author: Phil Sutter +Date: Thu Oct 16 00:00:05 2025 +0200 + + tests: py: Do not rely upon '[end]' marker + + Set element lines reliably start with whitespace followed by the word "element" + and are separated by the same pattern. Use it instead of '[end]' (or anything + enclosed in brackets). + + While at it, recognize payload lines as starting with ' [ ' and avoid + searching for the closing bracket. + + Signed-off-by: Phil Sutter + +Signed-off-by: Phil Sutter +--- + tests/py/nft-test.py | 18 +++++++++--------- + 1 file changed, 9 insertions(+), 9 deletions(-) + +diff --git a/tests/py/nft-test.py b/tests/py/nft-test.py +index 6eddc8b..007b6a6 100755 +--- a/tests/py/nft-test.py ++++ b/tests/py/nft-test.py +@@ -685,7 +685,7 @@ def output_clean(pre_output, chain): + def payload_check_elems_to_set(elems): + newset = set() + +- for n, line in enumerate(elems.split('[end]')): ++ for n, line in enumerate(elems.split("element")): + e = line.strip() + if e in newset: + print_error("duplicate", e, n) +@@ -697,17 +697,17 @@ def payload_check_elems_to_set(elems): + + + def payload_check_set_elems(want, got): +- if want.find('element') < 0 or want.find('[end]') < 0: +- return 0 +- +- if got.find('element') < 0 or got.find('[end]') < 0: +- return 0 ++ if not want.strip().startswith("element") or \ ++ not got.strip().startswith("element"): ++ return False + + set_want = payload_check_elems_to_set(want) + set_got = payload_check_elems_to_set(got) + + return set_want == set_got + ++def payload_line_relevant(line): ++ return line.startswith(' [ ') or line.strip().startswith("element") + + def payload_check(payload_buffer, file, cmd): + file.seek(0, 0) +@@ -718,11 +718,11 @@ def payload_check(payload_buffer, file, cmd): + + for lineno, want_line in enumerate(payload_buffer): + # skip irreleant parts, such as "ip test-ipv4 output" +- if want_line.find("[") < 0 or want_line.find("]") < 0: +- continue ++ if not payload_line_relevant(want_line): ++ continue + + line = file.readline() +- while line.find("[") < 0 or line.find("]") < 0 or (line.startswith("family ") and line.find(" [nf_tables]") > 0): ++ while not payload_line_relevant(line): + line = file.readline() + if line == "": + break diff --git a/0265-tests-py-inet-osf.t-Fix-element-ordering-in-JSON-equ.patch b/0265-tests-py-inet-osf.t-Fix-element-ordering-in-JSON-equ.patch new file mode 100644 index 0000000..5244913 --- /dev/null +++ b/0265-tests-py-inet-osf.t-Fix-element-ordering-in-JSON-equ.patch @@ -0,0 +1,121 @@ +From 7c2c36e0a564c5a7678c5dd3e56bf96ef96e61d1 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:15:25 +0200 +Subject: [PATCH] tests: py: inet/osf.t: Fix element ordering in JSON + equivalents + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit b028f8ce616bb5a219a10844357b9a3822d99a8c + +commit b028f8ce616bb5a219a10844357b9a3822d99a8c +Author: Phil Sutter +Date: Thu Oct 9 02:06:54 2025 +0200 + + tests: py: inet/osf.t: Fix element ordering in JSON equivalents + + The original rules order set elements differently. Stick to that and add + entries to inet/osf.t.json.output to cover for nftables reordering + entries. + + Fixes: 92029c1282958 ("src: osf: add json support") + Signed-off-by: Phil Sutter + +Signed-off-by: Phil Sutter +--- + tests/py/inet/osf.t.json | 12 ++++---- + tests/py/inet/osf.t.json.output | 53 +++++++++++++++++++++++++++++++++ + 2 files changed, 59 insertions(+), 6 deletions(-) + create mode 100644 tests/py/inet/osf.t.json.output + +diff --git a/tests/py/inet/osf.t.json b/tests/py/inet/osf.t.json +index cedb7f6..b1bf747 100644 +--- a/tests/py/inet/osf.t.json ++++ b/tests/py/inet/osf.t.json +@@ -73,8 +73,8 @@ + "op": "==", + "right": { + "set": [ +- "MacOs", +- "Windows" ++ "Windows", ++ "MacOs" + ] + } + } +@@ -114,13 +114,13 @@ + "map": { + "data": { + "set": [ +- [ +- "MacOs", +- 2 +- ], + [ + "Windows", + 1 ++ ], ++ [ ++ "MacOs", ++ 2 + ] + ] + }, +diff --git a/tests/py/inet/osf.t.json.output b/tests/py/inet/osf.t.json.output +new file mode 100644 +index 0000000..922e395 +--- /dev/null ++++ b/tests/py/inet/osf.t.json.output +@@ -0,0 +1,53 @@ ++# osf name { "Windows", "MacOs" } ++[ ++ { ++ "match": { ++ "left": { ++ "osf": { ++ "key": "name" ++ } ++ }, ++ "op": "==", ++ "right": { ++ "set": [ ++ "MacOs", ++ "Windows" ++ ] ++ } ++ } ++ } ++] ++ ++# ct mark set osf name map { "Windows" : 0x00000001, "MacOs" : 0x00000002 } ++[ ++ { ++ "mangle": { ++ "key": { ++ "ct": { ++ "key": "mark" ++ } ++ }, ++ "value": { ++ "map": { ++ "data": { ++ "set": [ ++ [ ++ "MacOs", ++ 2 ++ ], ++ [ ++ "Windows", ++ 1 ++ ] ++ ] ++ }, ++ "key": { ++ "osf": { ++ "key": "name" ++ } ++ } ++ } ++ } ++ } ++ } ++] diff --git a/0266-tests-py-Implement-payload_record.patch b/0266-tests-py-Implement-payload_record.patch new file mode 100644 index 0000000..7ff60d6 --- /dev/null +++ b/0266-tests-py-Implement-payload_record.patch @@ -0,0 +1,189 @@ +From 4f46fd3f7ff8e46c7df7d72b46e157f86c02e855 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:15:25 +0200 +Subject: [PATCH] tests: py: Implement payload_record() + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit b84762fd6e248e960e70c26453faf45a5f975e70 + +commit b84762fd6e248e960e70c26453faf45a5f975e70 +Author: Phil Sutter +Date: Wed Sep 10 15:14:23 2025 +0200 + + tests: py: Implement payload_record() + + This is a helper function to store payload records (and JSON + equivalents) in .got files. The code it replaces missed to insert a + newline before the new entry and also did not check for existing records + in all spots. + + Signed-off-by: Phil Sutter + +Signed-off-by: Phil Sutter +--- + tests/py/nft-test.py | 117 ++++++++++++++++++++++++++----------------- + 1 file changed, 71 insertions(+), 46 deletions(-) + +diff --git a/tests/py/nft-test.py b/tests/py/nft-test.py +index 007b6a6..b398c50 100755 +--- a/tests/py/nft-test.py ++++ b/tests/py/nft-test.py +@@ -16,6 +16,7 @@ + from __future__ import print_function + import sys + import os ++import io + import argparse + import signal + import json +@@ -741,6 +742,66 @@ def payload_check(payload_buffer, file, cmd): + return i > 0 + + ++def payload_record(path, rule, payload, desc="payload"): ++ ''' ++ Record payload for @rule in file at @path ++ ++ - @payload may be a file handle, a string or an array of strings ++ - Avoid duplicate entries by searching for a match first ++ - Separate entries by a single empty line, so check for trailing newlines ++ before writing ++ - @return False if already existing, True otherwise ++ ''' ++ try: ++ with open(path, 'r') as f: ++ lines = f.readlines() ++ except: ++ lines = [] ++ ++ plines = [] ++ if isinstance(payload, io.TextIOWrapper): ++ payload.seek(0, 0) ++ while True: ++ line = payload.readline() ++ if line.startswith("family "): ++ continue ++ if line == "": ++ break ++ plines.append(line) ++ elif isinstance(payload, str): ++ plines = [l + "\n" for l in payload.split("\n")] ++ elif isinstance(payload, list): ++ plines = payload ++ else: ++ raise Exception ++ ++ found = False ++ for i in range(len(lines)): ++ if lines[i] == rule + "\n": ++ found = True ++ for pline in plines: ++ i += 1 ++ if lines[i] != pline: ++ found = False ++ break ++ if found: ++ return False ++ ++ try: ++ with open(path, 'a') as f: ++ if len(lines) > 0 and lines[-1] != "\n": ++ f.write("\n") ++ f.write("# %s\n" % rule) ++ f.writelines(plines) ++ except: ++ warnfmt = "Failed to write %s for rule %s" ++ else: ++ warnfmt = "Wrote %s for rule %s" ++ ++ print_warning(warnfmt % (desc, rule[0]), os.path.basename(path), 1) ++ return True ++ ++ + def json_dump_normalize(json_string, human_readable = False): + json_obj = json.loads(json_string) + +@@ -865,28 +926,8 @@ def rule_add(rule, filename, lineno, force_all_family_option, filename_path): + if state == "ok" and not payload_check(table_payload_expected, + payload_log, cmd): + error += 1 +- +- try: +- gotf = open("%s.got" % table_payload_path) +- gotf_payload_expected = payload_find_expected(gotf, rule[0]) +- gotf.close() +- except: +- gotf_payload_expected = None +- payload_log.seek(0, 0) +- if not payload_check(gotf_payload_expected, payload_log, cmd): +- gotf = open("%s.got" % table_payload_path, 'a') +- payload_log.seek(0, 0) +- gotf.write("# %s\n" % rule[0]) +- while True: +- line = payload_log.readline() +- if line.startswith("family "): +- continue +- if line == "": +- break +- gotf.write(line) +- gotf.close() +- print_warning("Wrote payload for rule %s" % rule[0], +- gotf.name, 1) ++ payload_record("%s.got" % table_payload_path, ++ rule[0], payload_log) + + # Check for matching ruleset listing + numeric_proto_old = nftables.set_numeric_proto_output(True) +@@ -977,13 +1018,9 @@ def rule_add(rule, filename, lineno, force_all_family_option, filename_path): + json_output = item["rule"] + break + json_input = json.dumps(json_output["expr"], sort_keys = True) +- +- gotf = open("%s.json.got" % filename_path, 'a') +- jdump = json_dump_normalize(json_input, True) +- gotf.write("# %s\n%s\n\n" % (rule[0], jdump)) +- gotf.close() +- print_warning("Wrote JSON equivalent for rule %s" % rule[0], +- gotf.name, 1) ++ payload_record("%s.json.got" % filename_path, rule[0], ++ json_dump_normalize(json_input, True), ++ "JSON equivalent") + + table_flush(table, filename, lineno) + payload_log = tempfile.TemporaryFile(mode="w+") +@@ -1011,17 +1048,8 @@ def rule_add(rule, filename, lineno, force_all_family_option, filename_path): + # Check for matching payload + if not payload_check(table_payload_expected, payload_log, cmd): + error += 1 +- gotf = open("%s.json.payload.got" % filename_path, 'a') +- payload_log.seek(0, 0) +- gotf.write("# %s\n" % rule[0]) +- while True: +- line = payload_log.readline() +- if line == "": +- break +- gotf.write(line) +- gotf.close() +- print_warning("Wrote JSON payload for rule %s" % rule[0], +- gotf.name, 1) ++ payload_record("%s.json.payload.got" % filename_path, ++ rule[0], payload_log, "JSON payload") + + # Check for matching ruleset listing + numeric_proto_old = nftables.set_numeric_proto_output(True) +@@ -1047,12 +1075,9 @@ def rule_add(rule, filename, lineno, force_all_family_option, filename_path): + print_differences_warning(filename, lineno, + json_input, json_output, cmd) + error += 1 +- gotf = open("%s.json.output.got" % filename_path, 'a') +- jdump = json_dump_normalize(json_output, True) +- gotf.write("# %s\n%s\n\n" % (rule[0], jdump)) +- gotf.close() +- print_warning("Wrote JSON output for rule %s" % rule[0], +- gotf.name, 1) ++ payload_record("%s.json.output.got" % filename_path, rule[0], ++ json_dump_normalize(json_output, True), ++ "JSON output") + # prevent further warnings and .got file updates + json_expected = json_output + elif json_expected and json_output != json_expected: diff --git a/0267-doc-don-t-suggest-to-disable-GSO.patch b/0267-doc-don-t-suggest-to-disable-GSO.patch new file mode 100644 index 0000000..b3fd539 --- /dev/null +++ b/0267-doc-don-t-suggest-to-disable-GSO.patch @@ -0,0 +1,69 @@ +From cbb04fd1e16d51c0ffb1ca6938259ad0590f910c Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:15:25 +0200 +Subject: [PATCH] doc: don't suggest to disable GSO + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 35cd3e7cff079c561ba616c792d5fdf13f6bd331 + +commit 35cd3e7cff079c561ba616c792d5fdf13f6bd331 +Author: Ronan Pigott +Date: Sun Oct 6 09:36:03 2024 -0700 + + doc: don't suggest to disable GSO + + The kernel can form aggregate packets whether or not GSO is enabled. + Disabling GSO is not a useful suggestion in this case. + + Fixes: 05628cdd677d (doc: describe behaviour of {ip,ip6} length) + Signed-off-by: Florian Westphal + +Signed-off-by: Phil Sutter +--- + doc/payload-expression.txt | 26 ++++++++++++-------------- + 1 file changed, 12 insertions(+), 14 deletions(-) + +diff --git a/doc/payload-expression.txt b/doc/payload-expression.txt +index d648785..b806890 100644 +--- a/doc/payload-expression.txt ++++ b/doc/payload-expression.txt +@@ -134,13 +134,12 @@ Destination address | + ipv4_addr + |====================== + +-Careful with matching on *ip length*: If GRO/GSO is enabled, then the Linux +-kernel might aggregate several packets into one big packet that is larger than +-MTU. Moreover, if GRO/GSO maximum size is larger than 65535 (see man ip-link(8), +-specifically gro_ipv6_max_size and gso_ipv6_max_size), then *ip length* might +-be 0 for such jumbo packets. *meta length* allows you to match on the packet +-length including the IP header size. If you want to perform heuristics on the +-*ip length* field, then disable GRO/GSO. ++Careful with matching on *ip length*: The Linux kernel might aggregate several ++packets into one big packet that is larger than MTU. Moreover, if GRO/GSO ++maximum size is larger than 65535 (see man ip-link(8), specifically ++gro_ipv4_max_size and gso_ipv4_max_size), then *ip length* might be 0 for such ++jumbo packets. *meta length* allows you to match on the packet length including ++the IP header size. + + ICMP HEADER EXPRESSION + ~~~~~~~~~~~~~~~~~~~~~~ +@@ -252,13 +251,12 @@ Destination address | + ipv6_addr + |======================= + +-Careful with matching on *ip6 length*: If GRO/GSO is enabled, then the Linux +-kernel might aggregate several packets into one big packet that is larger than +-MTU. Moreover, if GRO/GSO maximum size is larger than 65535 (see man ip-link(8), +-specifically gro_ipv6_max_size and gso_ipv6_max_size), then *ip6 length* might +-be 0 for such jumbo packets. *meta length* allows you to match on the packet +-length including the IP header size. If you want to perform heuristics on the +-*ip6 length* field, then disable GRO/GSO. ++Careful with matching on *ip6 length*: The Linux kernel might aggregate several ++packets into one big packet that is larger than MTU. Moreover, if GRO/GSO ++maximum size is larger than 65535 (see man ip-link(8), specifically ++gro_max_size and gso_max_size), then *ip6 length* might be 0 for such ++jumbo packets. *meta length* allows you to match on the packet length including ++the IPv6 header size. + + .Using ip6 header expressions + ----------------------------- diff --git a/0268-doc-libnftables-json-Describe-RULESET-object.patch b/0268-doc-libnftables-json-Describe-RULESET-object.patch new file mode 100644 index 0000000..447ea26 --- /dev/null +++ b/0268-doc-libnftables-json-Describe-RULESET-object.patch @@ -0,0 +1,48 @@ +From c685bc58489a74c54991476f5da17a1864030d06 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:15:25 +0200 +Subject: [PATCH] doc: libnftables-json: Describe RULESET object + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 454f361434522bbeba32e114a14c336e1ebf20a1 + +commit 454f361434522bbeba32e114a14c336e1ebf20a1 +Author: Phil Sutter +Date: Thu Nov 6 12:14:56 2025 +0100 + + doc: libnftables-json: Describe RULESET object + + Document the syntax of this meta-object used by "list" and "flush" + commands only. + + Fixes: 872f373dc50f7 ("doc: Add JSON schema documentation") + Signed-off-by: Phil Sutter + +Signed-off-by: Phil Sutter +--- + doc/libnftables-json.adoc | 12 ++++++++++++ + 1 file changed, 12 insertions(+) + +diff --git a/doc/libnftables-json.adoc b/doc/libnftables-json.adoc +index 9129cc8..259c1e3 100644 +--- a/doc/libnftables-json.adoc ++++ b/doc/libnftables-json.adoc +@@ -200,6 +200,18 @@ Rename a chain. The new name is expected in a dedicated property named + + == RULESET ELEMENTS + ++=== RULESET ++[verse] ++____ ++*{ "ruleset":* 'RULSET_PROPERTIES' *}* ++ ++'RULESET_PROPERTIES' := *null* | *{ "family":* 'STRING' *}* ++____ ++ ++This is a special object for use with *list* and *flush* commands which will ++then operate on either the whole ruleset or the parts of it belonging to the ++given family. ++ + === TABLE + [verse] + ____ diff --git a/0269-rule-skip-CMD_OBJ_SETELEMS-with-no-elements-after-se.patch b/0269-rule-skip-CMD_OBJ_SETELEMS-with-no-elements-after-se.patch new file mode 100644 index 0000000..dfaf0a3 --- /dev/null +++ b/0269-rule-skip-CMD_OBJ_SETELEMS-with-no-elements-after-se.patch @@ -0,0 +1,41 @@ +From 8105f96d1a3c8e8f0294b35af4a45fe8fe1c7d3b Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:15:25 +0200 +Subject: [PATCH] rule: skip CMD_OBJ_SETELEMS with no elements after set flush + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 8b7a533f8f8b276bfa71dcb306d6857e54015234 + +commit 8b7a533f8f8b276bfa71dcb306d6857e54015234 +Author: Pablo Neira Ayuso +Date: Thu Nov 20 00:41:13 2025 +0100 + + rule: skip CMD_OBJ_SETELEMS with no elements after set flush + + Set declaration + set flush results in a crash because CMD_OBJ_SETELEMS + does not expect no elements. This internal command only shows up if set + contains elements, however, evaluation flushes set content after the set + expansion. Skip this command CMD_OBJ_SETELEMS if set is empty. + + Fixes: d3c8051cb767 ("rule: rework CMD_OBJ_SETELEMS logic") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/rule.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/src/rule.c b/src/rule.c +index f64cd68..30c3a87 100644 +--- a/src/rule.c ++++ b/src/rule.c +@@ -1476,6 +1476,9 @@ static int do_add_setelems(struct netlink_ctx *ctx, struct cmd *cmd, + { + struct set *set = cmd->set; + ++ if (!set->init) ++ return 0; ++ + return __do_add_elements(ctx, cmd, set, set->init, flags); + } + diff --git a/0270-tests-json_echo-Drop-rule-handle-before-multi-add.patch b/0270-tests-json_echo-Drop-rule-handle-before-multi-add.patch new file mode 100644 index 0000000..ff5a9ea --- /dev/null +++ b/0270-tests-json_echo-Drop-rule-handle-before-multi-add.patch @@ -0,0 +1,38 @@ +From 5e2b22338ba5f0a99d515ccdfffbadf7a8af7ec2 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:15:25 +0200 +Subject: [PATCH] tests: json_echo: Drop rule handle before multi-add + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit ad74cbd63a9782e8285f7198a5055d7947998b7f + +commit ad74cbd63a9782e8285f7198a5055d7947998b7f +Author: Phil Sutter +Date: Tue Jan 20 22:47:02 2026 +0100 + + tests: json_echo: Drop rule handle before multi-add + + Now that JSON parser respects rule handles in explicit add commands, the + still present rule handle causes an error since the old rule does not + exist anymore. + + Fixes: 50b5b71ebeee3 ("parser_json: Rewrite echo support") + Signed-off-by: Phil Sutter + +Signed-off-by: Phil Sutter +--- + tests/json_echo/run-test.py | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/tests/json_echo/run-test.py b/tests/json_echo/run-test.py +index a6bdfc6..7217746 100755 +--- a/tests/json_echo/run-test.py ++++ b/tests/json_echo/run-test.py +@@ -286,6 +286,7 @@ add_quota["add"]["quota"]["name"] = "q" + do_flush() + + print("doing multi add") ++del(add_rule["add"]["rule"]["handle"]) + add_multi = [ add_table, add_chain, add_set, add_rule ] + out = do_command(add_multi) + diff --git a/0271-monitor-fix-memleak-in-setelem-cb.patch b/0271-monitor-fix-memleak-in-setelem-cb.patch new file mode 100644 index 0000000..ace834b --- /dev/null +++ b/0271-monitor-fix-memleak-in-setelem-cb.patch @@ -0,0 +1,53 @@ +From 5a2bd880073087f7e6238ec87d691cc9511eda9d Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:15:25 +0200 +Subject: [PATCH] monitor: fix memleak in setelem cb + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit d2a427c4abfadd2ce51bdab54c40fbf3d990c724 + +commit d2a427c4abfadd2ce51bdab54c40fbf3d990c724 +Author: Florian Westphal +Date: Wed Jan 21 14:33:21 2026 +0100 + + monitor: fix memleak in setelem cb + + since 4521732ebbf3 ("monitor: missing cache and set handle initialization") + these fields are set via handle_merge(), so don't clobber those + fields in json output case: + + ==31877==ERROR: LeakSanitizer: detected memory leaks + Direct leak of 16 byte(s) in 2 object(s) allocated from: + #0 0x7f0cb9f29d4b in strdup asan/asan_interceptors.cpp:593 + #1 0x7f0cb9b584fd in xstrdup src/utils.c:80 + #2 0x7f0cb9b355b3 in handle_merge src/rule.c:127 + #3 0x7f0cb9ae12b8 in netlink_events_setelem_cb src/monitor.c:457 + + Seen when running tests/monitor with asan enabled. + + Fixes: 4521732ebbf3 ("monitor: missing cache and set handle initialization") + Signed-off-by: Florian Westphal + +Signed-off-by: Phil Sutter +--- + src/monitor.c | 6 ------ + 1 file changed, 6 deletions(-) + +diff --git a/src/monitor.c b/src/monitor.c +index b9b0263..448a106 100644 +--- a/src/monitor.c ++++ b/src/monitor.c +@@ -496,13 +496,7 @@ static int netlink_events_setelem_cb(const struct nlmsghdr *nlh, int type, + nft_mon_print(monh, "\n"); + break; + case NFTNL_OUTPUT_JSON: +- dummyset->handle.family = family; +- dummyset->handle.set.name = setname; +- dummyset->handle.table.name = table; + monitor_print_element_json(monh, cmd, dummyset); +- /* prevent set_free() from trying to free those */ +- dummyset->handle.set.name = NULL; +- dummyset->handle.table.name = NULL; + if (!nft_output_echo(&monh->ctx->nft->output)) + nft_mon_print(monh, "\n"); + break; diff --git a/0272-tcpopt-add-symbol-table-for-mptcp-suboptions.patch b/0272-tcpopt-add-symbol-table-for-mptcp-suboptions.patch new file mode 100644 index 0000000..d5b18ae --- /dev/null +++ b/0272-tcpopt-add-symbol-table-for-mptcp-suboptions.patch @@ -0,0 +1,269 @@ +From 122194c2f1742891d8a22264f294f8882ea304d2 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:15:25 +0200 +Subject: [PATCH] tcpopt: add symbol table for mptcp suboptions + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 9832c2c347eb09eb8790e2ea003855f92b2ac257 + +commit 9832c2c347eb09eb8790e2ea003855f92b2ac257 +Author: Florian Westphal +Date: Thu Feb 27 15:52:07 2025 +0100 + + tcpopt: add symbol table for mptcp suboptions + + nft can be used t match on specific multipath tcp subtypes: + + tcp option mptcp subtype 0 + + However, depending on which subtype to match, users need to look up the + type/value to use in rfc8684. Add support for mnemonics and + "nft describe tcp option mptcp subtype" to get the subtype list. + + Because the number of unique 'enum datatypes' is limited by ABI contraints + this adds a new mptcp suboption type as integer alias. + + After this patch, nft supports all of the following: + add element t s { mp-capable } + add rule t c tcp option mptcp subtype mp-capable + add rule t c tcp option mptcp subtype { mp-capable, mp-fail } + + For the 3rd case, listing will break because unlike for named sets, nft + lacks the type information needed to pretty-print the integer values, + i.e. nft will print the 3rd rule as 'subtype { 0, 6 }'. + + This is resolved in a followup patch. + + Other problematic constructs are: + set s1 { + typeof tcp option mptcp subtype . ip saddr + elements = { mp-fail . 1.2.3.4 } + } + + Followed by: + tcp option mptcp subtype . ip saddr @s1 + + nft will print this as: + tcp option mptcp unknown & 240) >> 4 . ip saddr @s1 + + All of these issues are not related to this patch, however, they also occur + with other bit-sized extheader fields. + + Signed-off-by: Florian Westphal + +Signed-off-by: Phil Sutter +--- + include/datatype.h | 5 +++- + src/tcpopt.c | 30 ++++++++++++++++++- + tests/py/any/tcpopt.t | 4 +-- + tests/py/any/tcpopt.t.json | 6 ++-- + tests/py/any/tcpopt.t.payload | 2 +- + .../testcases/sets/dumps/typeof_sets_0.nft | 9 ++++++ + tests/shell/testcases/sets/typeof_sets_0 | 18 +++++++++++ + 7 files changed, 66 insertions(+), 8 deletions(-) + +diff --git a/include/datatype.h b/include/datatype.h +index 68e12d5..e394003 100644 +--- a/include/datatype.h ++++ b/include/datatype.h +@@ -258,7 +258,6 @@ extern const struct datatype verdict_type; + extern const struct datatype nfproto_type; + extern const struct datatype bitmask_type; + extern const struct datatype integer_type; +-extern const struct datatype xinteger_type; + extern const struct datatype string_type; + extern const struct datatype lladdr_type; + extern const struct datatype ipaddr_type; +@@ -285,6 +284,10 @@ extern const struct datatype reject_icmp_code_type; + extern const struct datatype reject_icmpv6_code_type; + extern const struct datatype reject_icmpx_code_type; + ++/* TYPE_INTEGER aliases: */ ++extern const struct datatype xinteger_type; ++extern const struct datatype mptcpopt_subtype; ++ + void inet_service_type_print(const struct expr *expr, struct output_ctx *octx); + + extern const struct datatype *concat_type_alloc(uint32_t type); +diff --git a/src/tcpopt.c b/src/tcpopt.c +index 8111a50..094687a 100644 +--- a/src/tcpopt.c ++++ b/src/tcpopt.c +@@ -108,6 +108,31 @@ static const struct exthdr_desc tcpopt_md5sig = { + }, + }; + ++static const struct symbol_table mptcp_subtype_tbl = { ++ .base = BASE_DECIMAL, ++ .symbols = { ++ SYMBOL("mp-capable", 0), ++ SYMBOL("mp-join", 1), ++ SYMBOL("dss", 2), ++ SYMBOL("add-addr", 3), ++ SYMBOL("remove-addr", 4), ++ SYMBOL("mp-prio", 5), ++ SYMBOL("mp-fail", 6), ++ SYMBOL("mp-fastclose", 7), ++ SYMBOL("mp-tcprst", 8), ++ SYMBOL_LIST_END ++ }, ++}; ++ ++/* alias of integer_type to parse mptcp subtypes */ ++const struct datatype mptcpopt_subtype = { ++ .type = TYPE_INTEGER, ++ .name = "integer", ++ .desc = "mptcp option subtype", ++ .size = 4, ++ .basetype = &integer_type, ++ .sym_tbl = &mptcp_subtype_tbl, ++}; + + static const struct exthdr_desc tcpopt_mptcp = { + .name = "mptcp", +@@ -115,7 +140,10 @@ static const struct exthdr_desc tcpopt_mptcp = { + .templates = { + [TCPOPT_MPTCP_KIND] = PHT("kind", 0, 8), + [TCPOPT_MPTCP_LENGTH] = PHT("length", 8, 8), +- [TCPOPT_MPTCP_SUBTYPE] = PHT("subtype", 16, 4), ++ [TCPOPT_MPTCP_SUBTYPE] = PROTO_HDR_TEMPLATE("subtype", ++ &mptcpopt_subtype, ++ BYTEORDER_BIG_ENDIAN, ++ 16, 4), + }, + }; + +diff --git a/tests/py/any/tcpopt.t b/tests/py/any/tcpopt.t +index 177f01c..a2fcdb3 100644 +--- a/tests/py/any/tcpopt.t ++++ b/tests/py/any/tcpopt.t +@@ -51,8 +51,8 @@ tcp option md5sig exists;ok + tcp option fastopen exists;ok + tcp option mptcp exists;ok + +-tcp option mptcp subtype 0;ok +-tcp option mptcp subtype 1;ok ++tcp option mptcp subtype mp-capable;ok ++tcp option mptcp subtype 1;ok;tcp option mptcp subtype mp-join + tcp option mptcp subtype { 0, 2};ok + + reset tcp option mptcp;ok +diff --git a/tests/py/any/tcpopt.t.json b/tests/py/any/tcpopt.t.json +index 4466f14..bf10c55 100644 +--- a/tests/py/any/tcpopt.t.json ++++ b/tests/py/any/tcpopt.t.json +@@ -533,7 +533,7 @@ + } + ] + +-# tcp option mptcp subtype 0 ++# tcp option mptcp subtype mp-capable + [ + { + "match": { +@@ -544,7 +544,7 @@ + } + }, + "op": "==", +- "right": 0 ++ "right": "mp-capable" + } + } + ] +@@ -560,7 +560,7 @@ + } + }, + "op": "==", +- "right": 1 ++ "right": "mp-join" + } + } + ] +diff --git a/tests/py/any/tcpopt.t.payload b/tests/py/any/tcpopt.t.payload +index 99b8985..e3cf500 100644 +--- a/tests/py/any/tcpopt.t.payload ++++ b/tests/py/any/tcpopt.t.payload +@@ -168,7 +168,7 @@ inet + [ exthdr load tcpopt 1b @ 30 + 0 present => reg 1 ] + [ cmp eq reg 1 0x00000001 ] + +-# tcp option mptcp subtype 0 ++# tcp option mptcp subtype mp-capable + inet + [ exthdr load tcpopt 1b @ 30 + 2 => reg 1 ] + [ bitwise reg 1 = ( reg 1 & 0x000000f0 ) ^ 0x00000000 ] +diff --git a/tests/shell/testcases/sets/dumps/typeof_sets_0.nft b/tests/shell/testcases/sets/dumps/typeof_sets_0.nft +index 63fc5b1..ed45d84 100644 +--- a/tests/shell/testcases/sets/dumps/typeof_sets_0.nft ++++ b/tests/shell/testcases/sets/dumps/typeof_sets_0.nft +@@ -60,6 +60,11 @@ table inet t { + elements = { "eth0" . 10.1.1.2 . exists } + } + ++ set s13 { ++ typeof tcp option mptcp subtype ++ elements = { mp-join, dss } ++ } ++ + chain c1 { + osf name @s1 accept + } +@@ -103,4 +108,8 @@ table inet t { + chain c12 { + iifname . ip saddr . meta ipsec @s12 accept + } ++ ++ chain c13 { ++ tcp option mptcp subtype @s13 accept ++ } + } +diff --git a/tests/shell/testcases/sets/typeof_sets_0 b/tests/shell/testcases/sets/typeof_sets_0 +index baf7c29..06c2f63 100755 +--- a/tests/shell/testcases/sets/typeof_sets_0 ++++ b/tests/shell/testcases/sets/typeof_sets_0 +@@ -99,6 +99,11 @@ INPUT="table inet t {$INPUT_OSF_SET + typeof meta iifname . ip saddr . meta ipsec + elements = { \"eth0\" . 10.1.1.2 . 1 } + } ++ ++ set s13 { ++ typeof tcp option mptcp subtype ++ elements = { mp-join, dss } ++ } + $INPUT_OSF_CHAIN + chain c2 { + ether type vlan vlan id @s2 accept +@@ -135,6 +140,10 @@ $INPUT_SCTP_CHAIN + chain c12 { + meta iifname . ip saddr . meta ipsec @s12 accept + } ++ ++ chain c13 { ++ tcp option mptcp subtype @s13 accept ++ } + }" + + EXPECTED="table inet t {$INPUT_OSF_SET +@@ -193,6 +202,11 @@ EXPECTED="table inet t {$INPUT_OSF_SET + typeof iifname . ip saddr . meta ipsec + elements = { \"eth0\" . 10.1.1.2 . exists } + } ++ ++ set s13 { ++ typeof tcp option mptcp subtype ++ elements = { mp-join, dss } ++ } + $INPUT_OSF_CHAIN + chain c2 { + vlan id @s2 accept +@@ -229,6 +243,10 @@ $INPUT_SCTP_CHAIN + chain c12 { + iifname . ip saddr . meta ipsec @s12 accept + } ++ ++ chain c13 { ++ tcp option mptcp subtype @s13 accept ++ } + }" + + diff --git a/0273-netlink_delinearize-also-consider-exthdr-type-when-t.patch b/0273-netlink_delinearize-also-consider-exthdr-type-when-t.patch new file mode 100644 index 0000000..12ff52c --- /dev/null +++ b/0273-netlink_delinearize-also-consider-exthdr-type-when-t.patch @@ -0,0 +1,144 @@ +From 0fa73aa3252123a2632b11f2bdcd3c17d1484094 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:15:25 +0200 +Subject: [PATCH] netlink_delinearize: also consider exthdr type when trimming + binops + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 01fe0f07a0ed9b3882fed82dcdfbae0ab1a3b04e + +commit 01fe0f07a0ed9b3882fed82dcdfbae0ab1a3b04e +Author: Florian Westphal +Date: Thu Feb 27 15:52:09 2025 +0100 + + netlink_delinearize: also consider exthdr type when trimming binops + + This allows trimming the binop for exthdrs, this will make nft render + (tcp option mptcp unknown & 240) >> 4 . ip saddr @s1 + + as + tcp option mptcp subtype . ip saddr @s1 + + Also extend the typeof set tests with a set concatenating a + sub-byte-sized exthdr expression with a payload one. + + The additional call to expr_postprocess() is needed, without this, + typeof_sets_0.nft fails because + frag frag-off @s4 accept + + is shown as + meta nfproto ipv6 frag frag-off @s4 accept + + Previouly, EXPR_EXTHDR would cause payload_binop_postprocess() + to return false which will then make the caller invoke + expr_postprocess(), but after handling EXPR_EXTHDR this doesn't happen + anymore. + + Signed-off-by: Florian Westphal + +Signed-off-by: Phil Sutter +--- + src/netlink_delinearize.c | 10 +++++++++- + .../testcases/sets/dumps/typeof_sets_0.nft | 10 ++++++++++ + tests/shell/testcases/sets/typeof_sets_0 | 19 +++++++++++++++++++ + 3 files changed, 38 insertions(+), 1 deletion(-) + +diff --git a/src/netlink_delinearize.c b/src/netlink_delinearize.c +index 95bc7a3..a0c6ac8 100644 +--- a/src/netlink_delinearize.c ++++ b/src/netlink_delinearize.c +@@ -2610,8 +2610,16 @@ static bool payload_binop_postprocess(struct rule_pp_ctx *ctx, + if (expr->left->etype != EXPR_BINOP || expr->left->op != OP_AND) + return false; + +- if (expr->left->left->etype != EXPR_PAYLOAD) ++ switch (expr->left->left->etype) { ++ case EXPR_EXTHDR: ++ break; ++ case EXPR_PAYLOAD: ++ break; ++ default: + return false; ++ } ++ ++ expr_postprocess(ctx, &expr->left->left); + + expr_set_type(expr->right, &integer_type, + BYTEORDER_HOST_ENDIAN); +diff --git a/tests/shell/testcases/sets/dumps/typeof_sets_0.nft b/tests/shell/testcases/sets/dumps/typeof_sets_0.nft +index ed45d84..34aaab6 100644 +--- a/tests/shell/testcases/sets/dumps/typeof_sets_0.nft ++++ b/tests/shell/testcases/sets/dumps/typeof_sets_0.nft +@@ -65,6 +65,12 @@ table inet t { + elements = { mp-join, dss } + } + ++ set s14 { ++ typeof tcp option mptcp subtype . ip daddr ++ elements = { remove-addr . 10.1.1.1, ++ mp-join . 10.1.1.2 } ++ } ++ + chain c1 { + osf name @s1 accept + } +@@ -112,4 +118,8 @@ table inet t { + chain c13 { + tcp option mptcp subtype @s13 accept + } ++ ++ chain c14 { ++ tcp option mptcp subtype . ip saddr @s14 accept ++ } + } +diff --git a/tests/shell/testcases/sets/typeof_sets_0 b/tests/shell/testcases/sets/typeof_sets_0 +index 06c2f63..dbe7054 100755 +--- a/tests/shell/testcases/sets/typeof_sets_0 ++++ b/tests/shell/testcases/sets/typeof_sets_0 +@@ -104,6 +104,11 @@ INPUT="table inet t {$INPUT_OSF_SET + typeof tcp option mptcp subtype + elements = { mp-join, dss } + } ++ ++ set s14 { ++ typeof tcp option mptcp subtype . ip daddr ++ elements = { remove-addr . 10.1.1.1, mp-join . 10.1.1.2 } ++ } + $INPUT_OSF_CHAIN + chain c2 { + ether type vlan vlan id @s2 accept +@@ -144,6 +149,10 @@ $INPUT_SCTP_CHAIN + chain c13 { + tcp option mptcp subtype @s13 accept + } ++ ++ chain c14 { ++ tcp option mptcp subtype . ip saddr @s14 accept ++ } + }" + + EXPECTED="table inet t {$INPUT_OSF_SET +@@ -207,6 +216,12 @@ EXPECTED="table inet t {$INPUT_OSF_SET + typeof tcp option mptcp subtype + elements = { mp-join, dss } + } ++ ++ set s14 { ++ typeof tcp option mptcp subtype . ip daddr ++ elements = { remove-addr . 10.1.1.1, ++ mp-join . 10.1.1.2 } ++ } + $INPUT_OSF_CHAIN + chain c2 { + vlan id @s2 accept +@@ -247,6 +262,10 @@ $INPUT_SCTP_CHAIN + chain c13 { + tcp option mptcp subtype @s13 accept + } ++ ++ chain c14 { ++ tcp option mptcp subtype . ip saddr @s14 accept ++ } + }" + + diff --git a/0274-expression-propagate-key-datatype-for-anonymous-sets.patch b/0274-expression-propagate-key-datatype-for-anonymous-sets.patch new file mode 100644 index 0000000..7e86e68 --- /dev/null +++ b/0274-expression-propagate-key-datatype-for-anonymous-sets.patch @@ -0,0 +1,178 @@ +From f1fd3a56922f88551c2a2c94654aa5175f6d410c Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:30:58 +0200 +Subject: [PATCH] expression: propagate key datatype for anonymous sets + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 3d6ad54dbc74449e171823cdaf9d43cd41721b8d + +commit 3d6ad54dbc74449e171823cdaf9d43cd41721b8d +Author: Florian Westphal +Date: Thu Feb 27 15:52:08 2025 +0100 + + expression: propagate key datatype for anonymous sets + + set s { + typeof tcp option mptcp subtype + elements = { mp-join, dss } + } + + is listed correctly. The set key provides the 'mptcpopt_subtype' + information and listing can print all elements with symbolic names. + + In anon set case this doesn't work: + tcp option mptcp subtype { mp-join, dss } + + is printed as "... subtype { 1, 2}" because the anon set only provides + plain integer type. + + This change propagates the datatype to the individual members of the + anon set. + + After this change, multiple existing data types such as TYPE_ICMP_TYPE + could theoretically be replaced by integer-type aliases. + + However, those datatypes are already exposed to userspace via the + 'set type' keyword. Thus removing them will break set definitions that + use them. + + Signed-off-by: Florian Westphal + +Signed-off-by: Phil Sutter +--- + src/expression.c | 35 +++++++++++++++++++++++++++++++++++ + tests/py/any/tcpopt.t | 2 +- + tests/py/any/tcpopt.t.json | 11 ++++++++--- + tests/py/any/tcpopt.t.payload | 10 +++++----- + 4 files changed, 49 insertions(+), 9 deletions(-) + +diff --git a/src/expression.c b/src/expression.c +index 09449da..4fd0a91 100644 +--- a/src/expression.c ++++ b/src/expression.c +@@ -1365,6 +1365,32 @@ static void set_ref_expr_destroy(struct expr *expr) + set_free(expr->set); + } + ++static void set_ref_expr_set_type(const struct expr *expr, ++ const struct datatype *dtype, ++ enum byteorder byteorder) ++{ ++ const struct set *s = expr->set; ++ ++ /* normal sets already have a precise datatype that is given in ++ * the set definition via type foo. ++ * ++ * Anon sets do not have this, and need to rely on type info ++ * generated at rule creation time. ++ * ++ * For most cases, the type info is correct. ++ * In some cases however, the kernel only stores TYPE_INTEGER. ++ * ++ * This happens with expressions that only use an integer alias ++ * type, e.g. the mptcpopt_subtype datatype. ++ * ++ * In this case nft will print the elements as numerical values ++ * because the base type lacks the ->sym_tbl information of the ++ * subtypes. ++ */ ++ if (s->init && set_is_anonymous(s->flags)) ++ expr_set_type(s->init, dtype, byteorder); ++} ++ + static const struct expr_ops set_ref_expr_ops = { + .type = EXPR_SET_REF, + .name = "set reference", +@@ -1372,6 +1398,7 @@ static const struct expr_ops set_ref_expr_ops = { + .json = set_ref_expr_json, + .clone = set_ref_expr_clone, + .destroy = set_ref_expr_destroy, ++ .set_type = set_ref_expr_set_type, + }; + + struct expr *set_ref_expr_alloc(const struct location *loc, struct set *set) +@@ -1431,6 +1458,13 @@ static void set_elem_expr_clone(struct expr *new, const struct expr *expr) + __set_elem_expr_clone(new, expr); + } + ++static void set_elem_expr_set_type(const struct expr *expr, ++ const struct datatype *dtype, ++ enum byteorder byteorder) ++{ ++ expr_set_type(expr->key, dtype, byteorder); ++} ++ + static const struct expr_ops set_elem_expr_ops = { + .type = EXPR_SET_ELEM, + .name = "set element", +@@ -1438,6 +1472,7 @@ static const struct expr_ops set_elem_expr_ops = { + .print = set_elem_expr_print, + .json = set_elem_expr_json, + .destroy = set_elem_expr_destroy, ++ .set_type = set_elem_expr_set_type, + }; + + struct expr *set_elem_expr_alloc(const struct location *loc, struct expr *key) +diff --git a/tests/py/any/tcpopt.t b/tests/py/any/tcpopt.t +index a2fcdb3..79699e2 100644 +--- a/tests/py/any/tcpopt.t ++++ b/tests/py/any/tcpopt.t +@@ -53,7 +53,7 @@ tcp option mptcp exists;ok + + tcp option mptcp subtype mp-capable;ok + tcp option mptcp subtype 1;ok;tcp option mptcp subtype mp-join +-tcp option mptcp subtype { 0, 2};ok ++tcp option mptcp subtype { mp-capable, mp-join, remove-addr, mp-prio, mp-fail, mp-fastclose, mp-tcprst };ok + + reset tcp option mptcp;ok + reset tcp option 2;ok;reset tcp option maxseg +diff --git a/tests/py/any/tcpopt.t.json b/tests/py/any/tcpopt.t.json +index bf10c55..fb0662a 100644 +--- a/tests/py/any/tcpopt.t.json ++++ b/tests/py/any/tcpopt.t.json +@@ -565,7 +565,7 @@ + } + ] + +-# tcp option mptcp subtype { 0, 2} ++# tcp option mptcp subtype { mp-capable, mp-join, remove-addr, mp-prio, mp-fail, mp-fastclose, mp-tcprst } + [ + { + "match": { +@@ -578,8 +578,13 @@ + "op": "==", + "right": { + "set": [ +- 0, +- 2 ++ "mp-capable", ++ "mp-join", ++ "remove-addr", ++ "mp-prio", ++ "mp-fail", ++ "mp-fastclose", ++ "mp-tcprst" + ] + } + } +diff --git a/tests/py/any/tcpopt.t.payload b/tests/py/any/tcpopt.t.payload +index e3cf500..af8c431 100644 +--- a/tests/py/any/tcpopt.t.payload ++++ b/tests/py/any/tcpopt.t.payload +@@ -180,11 +180,11 @@ inet + [ bitwise reg 1 = ( reg 1 & 0x000000f0 ) ^ 0x00000000 ] + [ cmp eq reg 1 0x00000010 ] + +-# tcp option mptcp subtype { 0, 2} +-__set%d test-inet 3 size 2 +-__set%d test-inet 0 +- element 00000000 : 0 [end] element 00000020 : 0 [end] +-inet ++# tcp option mptcp subtype { mp-capable, mp-join, remove-addr, mp-prio, mp-fail, mp-fastclose, mp-tcprst } ++__set%d test-ip4 3 size 7 ++__set%d test-ip4 0 ++ element 00000000 : 0 [end] element 00000010 : 0 [end] element 00000040 : 0 [end] element 00000050 : 0 [end] element 00000060 : 0 [end] element 00000070 : 0 [end] element 00000080 : 0 [end] ++ip test-ip4 input + [ exthdr load tcpopt 1b @ 30 + 2 => reg 1 ] + [ bitwise reg 1 = ( reg 1 & 0x000000f0 ) ^ 0x00000000 ] + [ lookup reg 1 set __set%d ] diff --git a/0275-expression-expr_build_udata_recurse-should-recurse.patch b/0275-expression-expr_build_udata_recurse-should-recurse.patch new file mode 100644 index 0000000..8415bb5 --- /dev/null +++ b/0275-expression-expr_build_udata_recurse-should-recurse.patch @@ -0,0 +1,183 @@ +From 5f9d328d5e098afb7ae69b72084380ffb472940f Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:30:58 +0200 +Subject: [PATCH] expression: expr_build_udata_recurse should recurse + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit d199cca92f9eb296ea527f4938d5454ce0b4cc8f + +commit d199cca92f9eb296ea527f4938d5454ce0b4cc8f +Author: Florian Westphal +Date: Thu Feb 27 15:52:10 2025 +0100 + + expression: expr_build_udata_recurse should recurse + + If we see EXPR_BINOP, recurse: ->left can be another EXPR_BINOP. + + This is irrelevant for 'typeof' named sets, but for anonymous sets, the + key is derived from the concat expression that builds the lookup key for + the anonymous set. + + tcp option mptcp subtype . ip daddr { mp-join. 10.0.0.1, .. + + needs two binops back-to-back: + + [ exthdr load tcpopt 1b @ 30 + 2 => reg 1 ] + [ bitwise reg 1 = ( reg 1 & 0x000000f0 ) ^ 0x00000000 ] + [ bitwise reg 1 = ( reg 1 >> 0x00000004 ) ] + + This bug prevents concat_expr_build_udata() from creating the userdata key + at load time. + + When listing the rules, we get an assertion: + nft: src/mergesort.c:23: concat_expr_msort_value: Assertion `ilen > 0' failed. + + because the set has a key with 0-length integers. + + Signed-off-by: Florian Westphal + +Signed-off-by: Phil Sutter +--- + src/expression.c | 2 +- + tests/py/any/tcpopt.t | 1 + + tests/py/any/tcpopt.t.json | 77 +++++++++++++++++++++++++++++++++++ + tests/py/any/tcpopt.t.payload | 13 ++++++ + 4 files changed, 92 insertions(+), 1 deletion(-) + +diff --git a/src/expression.c b/src/expression.c +index 4fd0a91..781d689 100644 +--- a/src/expression.c ++++ b/src/expression.c +@@ -954,7 +954,7 @@ static struct expr *expr_build_udata_recurse(struct expr *e) + { + switch (e->etype) { + case EXPR_BINOP: +- return e->left; ++ return expr_build_udata_recurse(e->left); + default: + break; + } +diff --git a/tests/py/any/tcpopt.t b/tests/py/any/tcpopt.t +index 79699e2..3d46c0e 100644 +--- a/tests/py/any/tcpopt.t ++++ b/tests/py/any/tcpopt.t +@@ -54,6 +54,7 @@ tcp option mptcp exists;ok + tcp option mptcp subtype mp-capable;ok + tcp option mptcp subtype 1;ok;tcp option mptcp subtype mp-join + tcp option mptcp subtype { mp-capable, mp-join, remove-addr, mp-prio, mp-fail, mp-fastclose, mp-tcprst };ok ++tcp option mptcp subtype . tcp dport { mp-capable . 10, mp-join . 100, add-addr . 200, remove-addr . 300, mp-prio . 400, mp-fail . 500, mp-fastclose . 600, mp-tcprst . 700 };ok + + reset tcp option mptcp;ok + reset tcp option 2;ok;reset tcp option maxseg +diff --git a/tests/py/any/tcpopt.t.json b/tests/py/any/tcpopt.t.json +index fb0662a..bc7b78a 100644 +--- a/tests/py/any/tcpopt.t.json ++++ b/tests/py/any/tcpopt.t.json +@@ -591,6 +591,83 @@ + } + ] + ++# tcp option mptcp subtype . tcp dport { mp-capable . 10, mp-join . 100, add-addr . 200, remove-addr . 300, mp-prio . 400, mp-fail . 500, mp-fastclose . 600, mp-tcprst . 700 } ++[ ++ { ++ "match": { ++ "left": { ++ "concat": [ ++ { ++ "tcp option": { ++ "field": "subtype", ++ "name": "mptcp" ++ } ++ }, ++ { ++ "payload": { ++ "field": "dport", ++ "protocol": "tcp" ++ } ++ } ++ ] ++ }, ++ "op": "==", ++ "right": { ++ "set": [ ++ { ++ "concat": [ ++ "mp-capable", ++ 10 ++ ] ++ }, ++ { ++ "concat": [ ++ "remove-addr", ++ 300 ++ ] ++ }, ++ { ++ "concat": [ ++ "mp-fastclose", ++ 600 ++ ] ++ }, ++ { ++ "concat": [ ++ "mp-join", ++ 100 ++ ] ++ }, ++ { ++ "concat": [ ++ "mp-prio", ++ 400 ++ ] ++ }, ++ { ++ "concat": [ ++ "mp-tcprst", ++ 700 ++ ] ++ }, ++ { ++ "concat": [ ++ "add-addr", ++ 200 ++ ] ++ }, ++ { ++ "concat": [ ++ "mp-fail", ++ 500 ++ ] ++ } ++ ] ++ } ++ } ++ } ++] ++ + # reset tcp option mptcp + [ + { +diff --git a/tests/py/any/tcpopt.t.payload b/tests/py/any/tcpopt.t.payload +index af8c431..437e073 100644 +--- a/tests/py/any/tcpopt.t.payload ++++ b/tests/py/any/tcpopt.t.payload +@@ -189,6 +189,19 @@ ip test-ip4 input + [ bitwise reg 1 = ( reg 1 & 0x000000f0 ) ^ 0x00000000 ] + [ lookup reg 1 set __set%d ] + ++# tcp option mptcp subtype . tcp dport { mp-capable . 10, mp-join . 100, add-addr . 200, remove-addr . 300, mp-prio . 400, mp-fail . 500, mp-fastclose . 600, mp-tcprst . 700 } ++__set%d test-ip4 3 ++__set%d test-ip4 0 ++ element 00000000 00000a00 : 0 [end] element 00000001 00006400 : 0 [end] element 00000003 0000c800 : 0 [end] element 00000004 00002c01 : 0 [end] element 00000005 00009001 : 0 [end] element 00000006 0000f401 : 0 [end] element 00000007 00005802 : 0 [end] element 00000008 0000bc02 : 0 [end] ++ip test-ip4 input ++ [ meta load l4proto => reg 1 ] ++ [ cmp eq reg 1 0x00000006 ] ++ [ exthdr load tcpopt 1b @ 30 + 2 => reg 1 ] ++ [ bitwise reg 1 = ( reg 1 & 0x000000f0 ) ^ 0x00000000 ] ++ [ bitwise reg 1 = ( reg 1 >> 0x00000004 ) ] ++ [ payload load 2b @ transport header + 2 => reg 9 ] ++ [ lookup reg 1 set __set%d ] ++ + # reset tcp option mptcp + ip test-ip4 input + [ exthdr reset tcpopt 30 ] diff --git a/0276-tests-py-any-tcpopt.t.json-Fix-JSON-equivalent.patch b/0276-tests-py-any-tcpopt.t.json-Fix-JSON-equivalent.patch new file mode 100644 index 0000000..b1dbe8d --- /dev/null +++ b/0276-tests-py-any-tcpopt.t.json-Fix-JSON-equivalent.patch @@ -0,0 +1,167 @@ +From 914a91d4878a22698ba6339113b0871103724096 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:30:58 +0200 +Subject: [PATCH] tests: py: any/tcpopt.t.json: Fix JSON equivalent + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 4282c50e4986cf0414282306dbff5fbad2f3c686 + +commit 4282c50e4986cf0414282306dbff5fbad2f3c686 +Author: Phil Sutter +Date: Wed Oct 8 23:46:34 2025 +0200 + + tests: py: any/tcpopt.t.json: Fix JSON equivalent + + Set element ordering differed from the rule in standard syntax. + + Fixes: d199cca92f9eb ("expression: expr_build_udata_recurse should recurse") + Signed-off-by: Phil Sutter + +Signed-off-by: Phil Sutter +--- + tests/py/any/tcpopt.t.json | 24 +++++----- + tests/py/any/tcpopt.t.json.output | 76 +++++++++++++++++++++++++++++++ + 2 files changed, 88 insertions(+), 12 deletions(-) + +diff --git a/tests/py/any/tcpopt.t.json b/tests/py/any/tcpopt.t.json +index bc7b78a..859d612 100644 +--- a/tests/py/any/tcpopt.t.json ++++ b/tests/py/any/tcpopt.t.json +@@ -622,20 +622,20 @@ + }, + { + "concat": [ +- "remove-addr", +- 300 ++ "mp-join", ++ 100 + ] + }, + { + "concat": [ +- "mp-fastclose", +- 600 ++ "add-addr", ++ 200 + ] + }, + { + "concat": [ +- "mp-join", +- 100 ++ "remove-addr", ++ 300 + ] + }, + { +@@ -646,20 +646,20 @@ + }, + { + "concat": [ +- "mp-tcprst", +- 700 ++ "mp-fail", ++ 500 + ] + }, + { + "concat": [ +- "add-addr", +- 200 ++ "mp-fastclose", ++ 600 + ] + }, + { + "concat": [ +- "mp-fail", +- 500 ++ "mp-tcprst", ++ 700 + ] + } + ] +diff --git a/tests/py/any/tcpopt.t.json.output b/tests/py/any/tcpopt.t.json.output +index ad0d25f..ae979e7 100644 +--- a/tests/py/any/tcpopt.t.json.output ++++ b/tests/py/any/tcpopt.t.json.output +@@ -30,3 +30,79 @@ + } + ] + ++# tcp option mptcp subtype . tcp dport { mp-capable . 10, mp-join . 100, add-addr . 200, remove-addr . 300, mp-prio . 400, mp-fail . 500, mp-fastclose . 600, mp-tcprst . 700 } ++[ ++ { ++ "match": { ++ "left": { ++ "concat": [ ++ { ++ "tcp option": { ++ "field": "subtype", ++ "name": "mptcp" ++ } ++ }, ++ { ++ "payload": { ++ "field": "dport", ++ "protocol": "tcp" ++ } ++ } ++ ] ++ }, ++ "op": "==", ++ "right": { ++ "set": [ ++ { ++ "concat": [ ++ "mp-capable", ++ 10 ++ ] ++ }, ++ { ++ "concat": [ ++ "remove-addr", ++ 300 ++ ] ++ }, ++ { ++ "concat": [ ++ "mp-fastclose", ++ 600 ++ ] ++ }, ++ { ++ "concat": [ ++ "mp-join", ++ 100 ++ ] ++ }, ++ { ++ "concat": [ ++ "mp-prio", ++ 400 ++ ] ++ }, ++ { ++ "concat": [ ++ "mp-tcprst", ++ 700 ++ ] ++ }, ++ { ++ "concat": [ ++ "add-addr", ++ 200 ++ ] ++ }, ++ { ++ "concat": [ ++ "mp-fail", ++ 500 ++ ] ++ } ++ ] ++ } ++ } ++ } ++] diff --git a/0277-mergesort-Fix-sorting-of-string-values.patch b/0277-mergesort-Fix-sorting-of-string-values.patch new file mode 100644 index 0000000..ed4cbbb --- /dev/null +++ b/0277-mergesort-Fix-sorting-of-string-values.patch @@ -0,0 +1,244 @@ +From 8ddff86558d1307e8cdd1f225f4633f867f272ed Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:30:58 +0200 +Subject: [PATCH] mergesort: Fix sorting of string values + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 340d904a974ed206fcc4d8ca32540762fd8e59e0 +Conflicts: Dropped changes to non-existent test and .json-nft dumps + +commit 340d904a974ed206fcc4d8ca32540762fd8e59e0 +Author: Phil Sutter +Date: Thu Nov 13 00:03:37 2025 +0100 + + mergesort: Fix sorting of string values + + Sorting order was obviously wrong, e.g. "ppp0" ordered before "eth1". + Moreover, this happened on Little Endian only so sorting order actually + depended on host's byteorder. By reimporting string values as Big + Endian, both issues are fixed: On one hand, GMP-internal byteorder no + longer depends on host's byteorder, on the other comparing strings + really starts with the first character, not the last. + + Fixes: 14ee0a979b622 ("src: sort set elements in netlink_get_setelems()") + Signed-off-by: Phil Sutter + +Signed-off-by: Phil Sutter +--- + src/mergesort.c | 7 +++ + tests/py/any/meta.t.json.output | 54 ------------------- + tests/py/any/queue.t.json.output | 4 +- + tests/py/inet/osf.t.json.output | 54 +++++++++++++++++++ + .../shell/testcases/maps/dumps/0012map_0.nft | 8 +-- + .../sets/dumps/sets_with_ifnames.nft | 2 +- + 6 files changed, 68 insertions(+), 61 deletions(-) + +diff --git a/src/mergesort.c b/src/mergesort.c +index 0452d60..65e8aa7 100644 +--- a/src/mergesort.c ++++ b/src/mergesort.c +@@ -37,6 +37,13 @@ static mpz_srcptr expr_msort_value(const struct expr *expr, mpz_t value) + case EXPR_RANGE: + return expr_msort_value(expr->left, value); + case EXPR_VALUE: ++ if (expr_basetype(expr)->type == TYPE_STRING) { ++ char buf[expr->len]; ++ ++ mpz_export_data(buf, expr->value, BYTEORDER_HOST_ENDIAN, expr->len); ++ mpz_import_data(value, buf, BYTEORDER_BIG_ENDIAN, expr->len); ++ return value; ++ } + return expr->value; + case EXPR_RANGE_VALUE: + return expr->range.low; +diff --git a/tests/py/any/meta.t.json.output b/tests/py/any/meta.t.json.output +index 4e9e669..329073e 100644 +--- a/tests/py/any/meta.t.json.output ++++ b/tests/py/any/meta.t.json.output +@@ -233,60 +233,6 @@ + } + ] + +-# meta iifname {"dummy0", "lo"} +-[ +- { +- "match": { +- "left": { +- "meta": { "key": "iifname" } +- }, +- "op": "==", +- "right": { +- "set": [ +- "lo", +- "dummy0" +- ] +- } +- } +- } +-] +- +-# meta iifname != {"dummy0", "lo"} +-[ +- { +- "match": { +- "left": { +- "meta": { "key": "iifname" } +- }, +- "op": "!=", +- "right": { +- "set": [ +- "lo", +- "dummy0" +- ] +- } +- } +- } +-] +- +-# meta oifname { "dummy0", "lo"} +-[ +- { +- "match": { +- "left": { +- "meta": { "key": "oifname" } +- }, +- "op": "==", +- "right": { +- "set": [ +- "lo", +- "dummy0" +- ] +- } +- } +- } +-] +- + # meta skuid {"bin", "root", "daemon"} accept + [ + { +diff --git a/tests/py/any/queue.t.json.output b/tests/py/any/queue.t.json.output +index ea37223..90670cc 100644 +--- a/tests/py/any/queue.t.json.output ++++ b/tests/py/any/queue.t.json.output +@@ -104,11 +104,11 @@ + 0 + ], + [ +- "ppp0", ++ "eth1", + 2 + ], + [ +- "eth1", ++ "ppp0", + 2 + ] + ] +diff --git a/tests/py/inet/osf.t.json.output b/tests/py/inet/osf.t.json.output +index 922e395..77ca7e3 100644 +--- a/tests/py/inet/osf.t.json.output ++++ b/tests/py/inet/osf.t.json.output +@@ -18,6 +18,26 @@ + } + ] + ++# osf version { "Windows:XP", "MacOs:Sierra" } ++[ ++ { ++ "match": { ++ "left": { ++ "osf": { ++ "key": "version" ++ } ++ }, ++ "op": "==", ++ "right": { ++ "set": [ ++ "MacOs:Sierra", ++ "Windows:XP" ++ ] ++ } ++ } ++ } ++] ++ + # ct mark set osf name map { "Windows" : 0x00000001, "MacOs" : 0x00000002 } + [ + { +@@ -51,3 +71,37 @@ + } + } + ] ++ ++# ct mark set osf version map { "Windows:XP" : 0x00000003, "MacOs:Sierra" : 0x00000004 } ++[ ++ { ++ "mangle": { ++ "key": { ++ "ct": { ++ "key": "mark" ++ } ++ }, ++ "value": { ++ "map": { ++ "data": { ++ "set": [ ++ [ ++ "MacOs:Sierra", ++ 4 ++ ], ++ [ ++ "Windows:XP", ++ 3 ++ ] ++ ] ++ }, ++ "key": { ++ "osf": { ++ "key": "version" ++ } ++ } ++ } ++ } ++ } ++ } ++] +diff --git a/tests/shell/testcases/maps/dumps/0012map_0.nft b/tests/shell/testcases/maps/dumps/0012map_0.nft +index 895490c..b199a00 100644 +--- a/tests/shell/testcases/maps/dumps/0012map_0.nft ++++ b/tests/shell/testcases/maps/dumps/0012map_0.nft +@@ -1,9 +1,9 @@ + table ip x { + map z { + type ifname : verdict +- elements = { "lo" : accept, +- "eth0" : drop, +- "eth1" : drop } ++ elements = { "eth0" : drop, ++ "eth1" : drop, ++ "lo" : accept } + } + + map w { +@@ -14,7 +14,7 @@ table ip x { + } + + chain y { +- iifname vmap { "lo" : accept, "eth0" : drop, "eth1" : drop } ++ iifname vmap { "eth0" : drop, "eth1" : drop, "lo" : accept } + } + + chain k { +diff --git a/tests/shell/testcases/sets/dumps/sets_with_ifnames.nft b/tests/shell/testcases/sets/dumps/sets_with_ifnames.nft +index 77a8baf..8abca03 100644 +--- a/tests/shell/testcases/sets/dumps/sets_with_ifnames.nft ++++ b/tests/shell/testcases/sets/dumps/sets_with_ifnames.nft +@@ -39,7 +39,7 @@ table inet testifsets { + chain v4icmp { + iifname @simple counter packets 0 bytes 0 + iifname @simple_wild counter packets 0 bytes 0 +- iifname { "eth0", "abcdef0" } counter packets 0 bytes 0 ++ iifname { "abcdef0", "eth0" } counter packets 0 bytes 0 + iifname { "abcdef*", "eth0" } counter packets 0 bytes 0 + iifname vmap @map_wild + } diff --git a/0278-mergesort-Align-concatenation-sort-order-with-Big-En.patch b/0278-mergesort-Align-concatenation-sort-order-with-Big-En.patch new file mode 100644 index 0000000..2de3a29 --- /dev/null +++ b/0278-mergesort-Align-concatenation-sort-order-with-Big-En.patch @@ -0,0 +1,360 @@ +From 635460aabe2945c48a011a2613d40ecfe2255423 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:31:06 +0200 +Subject: [PATCH] mergesort: Align concatenation sort order with Big Endian + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 0d819bcbbf36d324b5344ac29bd82a5017098c4b +Conflicts: Dropped changes to non-existent test and .json-nft dumps + +commit 0d819bcbbf36d324b5344ac29bd82a5017098c4b +Author: Phil Sutter +Date: Thu Nov 13 00:14:43 2025 +0100 + + mergesort: Align concatenation sort order with Big Endian + + By exporting all concat components in a way independent from host + byteorder and importing that blob of data in the same way aligns sort + order between hosts of different Endianness. + + Fixes: 741a06ac15d2b ("mergesort: find base value expression type via recursion") + Signed-off-by: Phil Sutter + +Signed-off-by: Phil Sutter +--- + src/mergesort.c | 4 +- + tests/py/any/ct.t.json.output | 20 ++--- + tests/py/any/tcpopt.t.json.output | 77 ------------------- + .../testcases/maps/dumps/typeof_integer_0.nft | 4 +- + .../nft-f/dumps/0012different_defines_0.nft | 2 +- + .../optimizations/dumps/merge_nat_inet.nft | 2 +- + .../optimizations/dumps/merge_reject.nft | 4 +- + .../dumps/merge_stmts_concat.nft | 8 +- + .../dumps/merge_stmts_concat_vmap.nft | 2 +- + .../sets/dumps/0029named_ifname_dtype_0.nft | 4 +- + .../dumps/0037_set_with_inet_service_0.nft | 8 +- + .../testcases/sets/dumps/typeof_sets_0.nft | 4 +- + tests/shell/testcases/sets/typeof_sets_0 | 4 +- + 13 files changed, 33 insertions(+), 110 deletions(-) + +diff --git a/src/mergesort.c b/src/mergesort.c +index 65e8aa7..ce3e395 100644 +--- a/src/mergesort.c ++++ b/src/mergesort.c +@@ -20,11 +20,11 @@ static void concat_expr_msort_value(const struct expr *expr, mpz_t value) + + list_for_each_entry(i, &expr->expressions, list) { + ilen = div_round_up(i->len, BITS_PER_BYTE); +- mpz_export_data(data + len, i->value, i->byteorder, ilen); ++ mpz_export_data(data + len, i->value, BYTEORDER_BIG_ENDIAN, ilen); + len += ilen; + } + +- mpz_import_data(value, data, BYTEORDER_HOST_ENDIAN, len); ++ mpz_import_data(value, data, BYTEORDER_BIG_ENDIAN, len); + } + + static mpz_srcptr expr_msort_value(const struct expr *expr, mpz_t value) +diff --git a/tests/py/any/ct.t.json.output b/tests/py/any/ct.t.json.output +index 82634c2..3f79593 100644 +--- a/tests/py/any/ct.t.json.output ++++ b/tests/py/any/ct.t.json.output +@@ -494,14 +494,14 @@ + "set": [ + { + "concat": [ +- "new", +- 305419896 ++ "established", ++ 309876276 + ] + }, + { + "concat": [ +- "established", +- 309876276 ++ "new", ++ 305419896 + ] + }, + { +@@ -578,23 +578,23 @@ + [ + { + "concat": [ +- "new", +- 305419896 ++ "established", ++ 2271560481 + ] + }, + { +- "drop": null ++ "accept": null + } + ], + [ + { + "concat": [ +- "established", +- 2271560481 ++ "new", ++ 305419896 + ] + }, + { +- "accept": null ++ "drop": null + } + ] + ] +diff --git a/tests/py/any/tcpopt.t.json.output b/tests/py/any/tcpopt.t.json.output +index ae979e7..4f170cf 100644 +--- a/tests/py/any/tcpopt.t.json.output ++++ b/tests/py/any/tcpopt.t.json.output +@@ -29,80 +29,3 @@ + } + } + ] +- +-# tcp option mptcp subtype . tcp dport { mp-capable . 10, mp-join . 100, add-addr . 200, remove-addr . 300, mp-prio . 400, mp-fail . 500, mp-fastclose . 600, mp-tcprst . 700 } +-[ +- { +- "match": { +- "left": { +- "concat": [ +- { +- "tcp option": { +- "field": "subtype", +- "name": "mptcp" +- } +- }, +- { +- "payload": { +- "field": "dport", +- "protocol": "tcp" +- } +- } +- ] +- }, +- "op": "==", +- "right": { +- "set": [ +- { +- "concat": [ +- "mp-capable", +- 10 +- ] +- }, +- { +- "concat": [ +- "remove-addr", +- 300 +- ] +- }, +- { +- "concat": [ +- "mp-fastclose", +- 600 +- ] +- }, +- { +- "concat": [ +- "mp-join", +- 100 +- ] +- }, +- { +- "concat": [ +- "mp-prio", +- 400 +- ] +- }, +- { +- "concat": [ +- "mp-tcprst", +- 700 +- ] +- }, +- { +- "concat": [ +- "add-addr", +- 200 +- ] +- }, +- { +- "concat": [ +- "mp-fail", +- 500 +- ] +- } +- ] +- } +- } +- } +-] +diff --git a/tests/shell/testcases/maps/dumps/typeof_integer_0.nft b/tests/shell/testcases/maps/dumps/typeof_integer_0.nft +index 19c24fe..7bd7daa 100644 +--- a/tests/shell/testcases/maps/dumps/typeof_integer_0.nft ++++ b/tests/shell/testcases/maps/dumps/typeof_integer_0.nft +@@ -8,8 +8,8 @@ table inet t { + + map m2 { + typeof udp length . @ih,32,32 : verdict +- elements = { 30 . 0x1e : drop, +- 20 . 0x24 : accept } ++ elements = { 20 . 0x24 : accept, ++ 30 . 0x1e : drop } + } + + chain c { +diff --git a/tests/shell/testcases/nft-f/dumps/0012different_defines_0.nft b/tests/shell/testcases/nft-f/dumps/0012different_defines_0.nft +index 4734b2f..a6e16e7 100644 +--- a/tests/shell/testcases/nft-f/dumps/0012different_defines_0.nft ++++ b/tests/shell/testcases/nft-f/dumps/0012different_defines_0.nft +@@ -8,7 +8,7 @@ table inet t { + ip6 daddr fe0::1 ip6 saddr fe0::2 + ip saddr vmap { 10.0.0.0 : drop, 10.0.0.2 : accept } + ip6 daddr vmap { fe0::1 : drop, fe0::2 : accept } +- ip6 saddr . ip6 nexthdr { fe0::2 . tcp, fe0::1 . udp } ++ ip6 saddr . ip6 nexthdr { fe0::1 . udp, fe0::2 . tcp } + ip daddr . iif vmap { 10.0.0.0 . "lo" : accept } + tcp dport 100-222 + udp dport vmap { 100-222 : accept } +diff --git a/tests/shell/testcases/optimizations/dumps/merge_nat_inet.nft b/tests/shell/testcases/optimizations/dumps/merge_nat_inet.nft +index a1a1135..1e08d5a 100644 +--- a/tests/shell/testcases/optimizations/dumps/merge_nat_inet.nft ++++ b/tests/shell/testcases/optimizations/dumps/merge_nat_inet.nft +@@ -1,7 +1,7 @@ + table inet nat { + chain prerouting { + oif "lo" accept +- dnat ip to iifname . ip daddr . tcp dport map { "enp2s0" . 72.2.3.70 . 80 : 10.1.1.52 . 80, "enp2s0" . 72.2.3.66 . 53122 : 10.1.1.10 . 22, "enp2s0" . 72.2.3.66 . 443 : 10.1.1.52 . 443 } ++ dnat ip to iifname . ip daddr . tcp dport map { "enp2s0" . 72.2.3.66 . 443 : 10.1.1.52 . 443, "enp2s0" . 72.2.3.66 . 53122 : 10.1.1.10 . 22, "enp2s0" . 72.2.3.70 . 80 : 10.1.1.52 . 80 } + } + + chain postrouting { +diff --git a/tests/shell/testcases/optimizations/dumps/merge_reject.nft b/tests/shell/testcases/optimizations/dumps/merge_reject.nft +index c29ad6d..1727d02 100644 +--- a/tests/shell/testcases/optimizations/dumps/merge_reject.nft ++++ b/tests/shell/testcases/optimizations/dumps/merge_reject.nft +@@ -1,13 +1,13 @@ + table ip x { + chain y { + ip daddr 172.30.33.70 tcp dport 3306 counter packets 0 bytes 0 drop +- meta l4proto . ip daddr . tcp dport { tcp . 172.30.238.117 . 8080, tcp . 172.30.33.71 . 3306, tcp . 172.30.254.251 . 3306 } counter packets 0 bytes 0 reject ++ meta l4proto . ip daddr . tcp dport { tcp . 172.30.33.71 . 3306, tcp . 172.30.238.117 . 8080, tcp . 172.30.254.251 . 3306 } counter packets 0 bytes 0 reject + ip daddr 172.30.254.252 tcp dport 3306 counter packets 0 bytes 0 reject with tcp reset + } + } + table ip6 x { + chain y { +- meta l4proto . ip6 daddr . tcp dport { tcp . aaaa::3 . 8080, tcp . aaaa::2 . 3306, tcp . aaaa::4 . 3306 } counter packets 0 bytes 0 reject ++ meta l4proto . ip6 daddr . tcp dport { tcp . aaaa::2 . 3306, tcp . aaaa::3 . 8080, tcp . aaaa::4 . 3306 } counter packets 0 bytes 0 reject + ip6 daddr aaaa::5 tcp dport 3306 counter packets 0 bytes 0 reject with tcp reset + } + } +diff --git a/tests/shell/testcases/optimizations/dumps/merge_stmts_concat.nft b/tests/shell/testcases/optimizations/dumps/merge_stmts_concat.nft +index d00ac41..6150258 100644 +--- a/tests/shell/testcases/optimizations/dumps/merge_stmts_concat.nft ++++ b/tests/shell/testcases/optimizations/dumps/merge_stmts_concat.nft +@@ -2,18 +2,18 @@ table ip x { + chain y { + iifname . ip saddr . ip daddr { "eth1" . 1.1.1.1 . 2.2.2.3, "eth1" . 1.1.1.2 . 2.2.2.4, "eth1" . 1.1.1.2 . 2.2.3.0/24, "eth1" . 1.1.1.2 . 2.2.4.0-2.2.4.10, "eth2" . 1.1.1.3 . 2.2.2.5 } accept + ip protocol . th dport { tcp . 22, udp . 67 } +- udp dport . ct state { 137 . new, 138 . new, 137 . untracked, 138 . untracked } accept ++ udp dport . ct state { 137 . new, 137 . untracked, 138 . new, 138 . untracked } accept + } + + chain c1 { +- udp dport . iifname { 51820 . "foo", 514 . "bar", 67 . "bar" } accept ++ udp dport . iifname { 67 . "bar", 514 . "bar", 51820 . "foo" } accept + } + + chain c2 { +- udp dport . iifname { 100 . "foo", 51820 . "foo", 514 . "bar", 67 . "bar" } accept ++ udp dport . iifname { 67 . "bar", 100 . "foo", 514 . "bar", 51820 . "foo" } accept + } + + chain c3 { +- udp dport . iifname { 100 . "foo", 51820 . "foo", 514 . "bar", 67 . "bar", 100 . "test", 51820 . "test" } accept ++ udp dport . iifname { 67 . "bar", 100 . "foo", 100 . "test", 514 . "bar", 51820 . "foo", 51820 . "test" } accept + } + } +diff --git a/tests/shell/testcases/optimizations/dumps/merge_stmts_concat_vmap.nft b/tests/shell/testcases/optimizations/dumps/merge_stmts_concat_vmap.nft +index 780aa09..81abb99 100644 +--- a/tests/shell/testcases/optimizations/dumps/merge_stmts_concat_vmap.nft ++++ b/tests/shell/testcases/optimizations/dumps/merge_stmts_concat_vmap.nft +@@ -1,6 +1,6 @@ + table ip x { + chain x { +- meta pkttype . udp dport vmap { broadcast . 547 : accept, broadcast . 67 : accept, multicast . 1900 : drop } ++ meta pkttype . udp dport vmap { broadcast . 67 : accept, broadcast . 547 : accept, multicast . 1900 : drop } + } + + chain y { +diff --git a/tests/shell/testcases/sets/dumps/0029named_ifname_dtype_0.nft b/tests/shell/testcases/sets/dumps/0029named_ifname_dtype_0.nft +index 6f9832a..e75d8a9 100644 +--- a/tests/shell/testcases/sets/dumps/0029named_ifname_dtype_0.nft ++++ b/tests/shell/testcases/sets/dumps/0029named_ifname_dtype_0.nft +@@ -12,8 +12,8 @@ table inet t { + type inet_service . ifname + elements = { 22 . "eth0", + 80 . "eth0", +- 81 . "eth0", +- 80 . "eth1" } ++ 80 . "eth1", ++ 81 . "eth0" } + } + + set nv { +diff --git a/tests/shell/testcases/sets/dumps/0037_set_with_inet_service_0.nft b/tests/shell/testcases/sets/dumps/0037_set_with_inet_service_0.nft +index 68b1f7b..0e85f7c 100644 +--- a/tests/shell/testcases/sets/dumps/0037_set_with_inet_service_0.nft ++++ b/tests/shell/testcases/sets/dumps/0037_set_with_inet_service_0.nft +@@ -1,11 +1,11 @@ + table inet filter { + set myset { + type ipv4_addr . inet_proto . inet_service +- elements = { 192.168.0.113 . tcp . 22, +- 192.168.0.12 . tcp . 53, +- 192.168.0.12 . udp . 53, ++ elements = { 192.168.0.12 . tcp . 53, + 192.168.0.12 . tcp . 80, +- 192.168.0.13 . tcp . 80 } ++ 192.168.0.12 . udp . 53, ++ 192.168.0.13 . tcp . 80, ++ 192.168.0.113 . tcp . 22 } + } + + chain forward { +diff --git a/tests/shell/testcases/sets/dumps/typeof_sets_0.nft b/tests/shell/testcases/sets/dumps/typeof_sets_0.nft +index 34aaab6..1ceddfc 100644 +--- a/tests/shell/testcases/sets/dumps/typeof_sets_0.nft ++++ b/tests/shell/testcases/sets/dumps/typeof_sets_0.nft +@@ -67,8 +67,8 @@ table inet t { + + set s14 { + typeof tcp option mptcp subtype . ip daddr +- elements = { remove-addr . 10.1.1.1, +- mp-join . 10.1.1.2 } ++ elements = { mp-join . 10.1.1.2, ++ remove-addr . 10.1.1.1 } + } + + chain c1 { +diff --git a/tests/shell/testcases/sets/typeof_sets_0 b/tests/shell/testcases/sets/typeof_sets_0 +index dbe7054..5ab6f0c 100755 +--- a/tests/shell/testcases/sets/typeof_sets_0 ++++ b/tests/shell/testcases/sets/typeof_sets_0 +@@ -219,8 +219,8 @@ EXPECTED="table inet t {$INPUT_OSF_SET + + set s14 { + typeof tcp option mptcp subtype . ip daddr +- elements = { remove-addr . 10.1.1.1, +- mp-join . 10.1.1.2 } ++ elements = { mp-join . 10.1.1.2, ++ remove-addr . 10.1.1.1 } + } + $INPUT_OSF_CHAIN + chain c2 { diff --git a/0279-segtree-Fix-range-aggregation-on-Big-Endian.patch b/0279-segtree-Fix-range-aggregation-on-Big-Endian.patch new file mode 100644 index 0000000..7727720 --- /dev/null +++ b/0279-segtree-Fix-range-aggregation-on-Big-Endian.patch @@ -0,0 +1,69 @@ +From c452efee3d900d1fc1c67f191a30ce75550303a2 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:19:32 +0200 +Subject: [PATCH] segtree: Fix range aggregation on Big Endian + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit e8b17865833b8c2b9f31a4dfb98069651572494d + +commit e8b17865833b8c2b9f31a4dfb98069651572494d +Author: Phil Sutter +Date: Tue Oct 21 18:36:10 2025 +0200 + + segtree: Fix range aggregation on Big Endian + + Interface name wildcards are received as ranges from prefix with zero + padding to prefix with ones padding. E.g. with "abcd*" (in hex): + + 61626364000000000000000000000000 - 61626364ffffffffffffffffffffffff + + The faulty code tries to export the prefix from the lower boundary (r1) + into a buffer, append "*" and allocate a constant expression from the + resulting string. This does not work on Big Endian though: + mpz_export_data() seems to zero-pad data upon export and not necessarily + respect the passed length value. Moreover, this padding appears in the + first bytes of the buffer. The amount of padding seems illogical, too: + While a 6B prefix causes 2B padding and 8B prefix no padding, 10B prefix + causes 4B padding and 12B prefix even 8B padding. + + Work around the odd behaviour by exporting the full data into a larger + buffer. + + A similar issue is caused by increasing the constant expression's length + to match the upper boundary data length: Data export when printing puts + the padding upfront, so the resulting string starts with NUL-chars. + Since this length adjustment seems not to have any effect in practice, + just drop it. + + Fixes: 88b2345a215ef ("segtree: add pretty-print support for wildcard strings in concatenated sets") + Signed-off-by: Phil Sutter + +Signed-off-by: Phil Sutter +--- + src/segtree.c | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +diff --git a/src/segtree.c b/src/segtree.c +index 291d877..b8ec99c 100644 +--- a/src/segtree.c ++++ b/src/segtree.c +@@ -409,16 +409,16 @@ void concat_range_aggregate(struct expr *set) + if (prefix_len >= 0 && + (prefix_len % BITS_PER_BYTE) == 0 && + string_type) { ++ unsigned int r1len = div_round_up(r1->len, BITS_PER_BYTE); + unsigned int str_len = prefix_len / BITS_PER_BYTE; +- char data[str_len + 2]; ++ char data[r1len + 1] = {}; + +- mpz_export_data(data, r1->value, BYTEORDER_HOST_ENDIAN, str_len); ++ mpz_export_data(data, r1->value, BYTEORDER_HOST_ENDIAN, r1len); + data[str_len] = '*'; + + tmp = constant_expr_alloc(&r1->location, r1->dtype, + BYTEORDER_HOST_ENDIAN, + (str_len + 1) * BITS_PER_BYTE, data); +- tmp->len = r2->len; + list_replace(&r2->list, &tmp->list); + r2_next = tmp->list.next; + expr_free(r2); diff --git a/0280-json-complete-multi-statement-set-element-support.patch b/0280-json-complete-multi-statement-set-element-support.patch new file mode 100644 index 0000000..cfe694f --- /dev/null +++ b/0280-json-complete-multi-statement-set-element-support.patch @@ -0,0 +1,41 @@ +From 9ea93637ba7186ec1486d1b8a32e6b0cc97eae0c Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:19:33 +0200 +Subject: [PATCH] json: complete multi-statement set element support + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 14dc90737a57bd1c6dc0ebc3eb30ab3090cc0afb +Conflicts: Dropped changes to non-existent .json-nft dumps + +commit 14dc90737a57bd1c6dc0ebc3eb30ab3090cc0afb +Author: Pablo Neira Ayuso +Date: Mon Feb 16 12:26:45 2026 +0100 + + json: complete multi-statement set element support + + Remove artificial limitation on the maximum number of statements per + element in listings. + + Moreover, update tests/shell which are currently incorrect. + + Fixes: e6d1d0d61195 ("src: add set element multi-statement support") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/json.c | 2 -- + 1 file changed, 2 deletions(-) + +diff --git a/src/json.c b/src/json.c +index 340c2cc..e26adce 100644 +--- a/src/json.c ++++ b/src/json.c +@@ -732,8 +732,6 @@ json_t *set_elem_expr_json(const struct expr *expr, struct output_ctx *octx) + /* XXX: detect and complain about clashes? */ + json_object_update_missing(root, tmp); + json_decref(tmp); +- /* TODO: only one statement per element. */ +- break; + } + return nft_json_pack("{s:o}", "elem", root); + } diff --git a/0281-parser_bison-add-range-check-for-synproxy-wscale.patch b/0281-parser_bison-add-range-check-for-synproxy-wscale.patch new file mode 100644 index 0000000..9a4f803 --- /dev/null +++ b/0281-parser_bison-add-range-check-for-synproxy-wscale.patch @@ -0,0 +1,135 @@ +From e1a3aad304be451bd1762ebfaaf8d4853b806a6b Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:19:43 +0200 +Subject: [PATCH] parser_bison: add range check for synproxy wscale + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit d6c09b7c7e86dcda6d933917b2ffbfd1c9ce3ff1 +Conflicts: Context change due to missing commits 35d9c77c57452 + ("src: add tunnel template support") and ffc40b38d58d3 + ("tunnel: add erspan support") + +commit d6c09b7c7e86dcda6d933917b2ffbfd1c9ce3ff1 +Author: Florian Westphal +Date: Wed Mar 11 18:52:31 2026 +0100 + + parser_bison: add range check for synproxy wscale + + After: nft -f wscale + Error: wscale must be in range 0-14 + wscale 15 + ^^ + + As-is the bogus value makes it to the kernel. Upcoming nf-next patch + adds futher checks to value attributes and will reject this. + + Also catch this from parser and fix the single_flag test case. + + Signed-off-by: Florian Westphal + +Signed-off-by: Phil Sutter +--- + src/parser_bison.y | 30 ++++++++++++++++++-------- + tests/shell/testcases/json/single_flag | 4 ++-- + 2 files changed, 23 insertions(+), 11 deletions(-) + +diff --git a/src/parser_bison.y b/src/parser_bison.y +index a5b70d8..0b49de2 100644 +--- a/src/parser_bison.y ++++ b/src/parser_bison.y +@@ -762,6 +762,7 @@ int nft_lex(void *, void *, void *); + %destructor { flowtable_free($$); } flowtable_block_alloc + + %type obj_block_alloc counter_block quota_block ct_helper_block ct_timeout_block ct_expect_block limit_block secmark_block synproxy_block ++%type synproxy_wscale + %destructor { obj_free($$); } obj_block_alloc + + %type stmt_list stateful_stmt_list set_elem_stmt_list +@@ -3752,14 +3753,25 @@ synproxy_args : synproxy_arg + | synproxy_args synproxy_arg + ; + ++synproxy_wscale : WSCALE NUM ++ { ++ if ($2 > 14) { ++ erec_queue(error(&@2, "wscale must be in range 0-14"), state->msgs); ++ YYERROR; ++ } ++ ++ $$ = $2; ++ } ++ ; ++ + synproxy_arg : MSS NUM + { + $0->synproxy.mss = $2; + $0->synproxy.flags |= NF_SYNPROXY_OPT_MSS; + } +- | WSCALE NUM ++ | synproxy_wscale + { +- $0->synproxy.wscale = $2; ++ $0->synproxy.wscale = $1; + $0->synproxy.flags |= NF_SYNPROXY_OPT_WSCALE; + } + | TIMESTAMP +@@ -3772,7 +3784,7 @@ synproxy_arg : MSS NUM + } + ; + +-synproxy_config : MSS NUM WSCALE NUM synproxy_ts synproxy_sack ++synproxy_config : MSS NUM synproxy_wscale synproxy_ts synproxy_sack + { + struct synproxy *synproxy; + uint32_t flags = 0; +@@ -3782,13 +3794,13 @@ synproxy_config : MSS NUM WSCALE NUM synproxy_ts synproxy_sack + flags |= NF_SYNPROXY_OPT_MSS; + synproxy->wscale = $4; + flags |= NF_SYNPROXY_OPT_WSCALE; ++ if ($4) ++ flags |= $4; + if ($5) + flags |= $5; +- if ($6) +- flags |= $6; + synproxy->flags = flags; + } +- | MSS NUM stmt_separator WSCALE NUM stmt_separator synproxy_ts synproxy_sack ++ | MSS NUM stmt_separator synproxy_wscale stmt_separator synproxy_ts synproxy_sack + { + struct synproxy *synproxy; + uint32_t flags = 0; +@@ -3796,12 +3808,12 @@ synproxy_config : MSS NUM WSCALE NUM synproxy_ts synproxy_sack + synproxy = &$0->synproxy; + synproxy->mss = $2; + flags |= NF_SYNPROXY_OPT_MSS; +- synproxy->wscale = $5; ++ synproxy->wscale = $4; + flags |= NF_SYNPROXY_OPT_WSCALE; ++ if ($6) ++ flags |= $6; + if ($7) + flags |= $7; +- if ($8) +- flags |= $8; + synproxy->flags = flags; + } + ; +diff --git a/tests/shell/testcases/json/single_flag b/tests/shell/testcases/json/single_flag +index fa917eb..7f36e72 100755 +--- a/tests/shell/testcases/json/single_flag ++++ b/tests/shell/testcases/json/single_flag +@@ -156,11 +156,11 @@ back_n_forth "$STD_SYNPROXY_2" "$JSON_SYNPROXY_2" + STD_SYNPROXY_OBJ_1="table ip t { + synproxy s { + mss 1280 +- wscale 64 ++ wscale 14 + sack-perm + } + }" +-JSON_SYNPROXY_OBJ_1='{"nftables": [{"table": {"family": "ip", "name": "t", "handle": 0}}, {"synproxy": {"family": "ip", "name": "s", "table": "t", "handle": 0, "mss": 1280, "wscale": 64, "flags": "sack-perm"}}]}' ++JSON_SYNPROXY_OBJ_1='{"nftables": [{"table": {"family": "ip", "name": "t", "handle": 0}}, {"synproxy": {"family": "ip", "name": "s", "table": "t", "handle": 0, "mss": 1280, "wscale": 14, "flags": "sack-perm"}}]}' + JSON_SYNPROXY_OBJ_1_EQUIV=$(sed 's/\("flags":\) \([^}]*\)/\1 [\2]/' <<< "$JSON_SYNPROXY_OBJ_1") + + STD_SYNPROXY_OBJ_2=$(sed 's/ \(sack-perm\)/timestamp \1/' <<< "$STD_SYNPROXY_OBJ_1") diff --git a/0282-cache-Relax-chain_cache_dump-filter-application.patch b/0282-cache-Relax-chain_cache_dump-filter-application.patch new file mode 100644 index 0000000..1f059c6 --- /dev/null +++ b/0282-cache-Relax-chain_cache_dump-filter-application.patch @@ -0,0 +1,45 @@ +From 96644b8c00d5cd96859b843d0b11c13e6af64c1b Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:20:05 +0200 +Subject: [PATCH] cache: Relax chain_cache_dump filter application + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 18cd6edf86139621dc6bfe0f17a391e46218c027 + +commit 18cd6edf86139621dc6bfe0f17a391e46218c027 +Author: Phil Sutter +Date: Fri Mar 6 17:02:17 2026 +0100 + + cache: Relax chain_cache_dump filter application + + While populating chain cache, a filter was only effective if it limited + fetching to both a table and a chain. Make it apply to 'list chains' + command as well which at most specifies a family and table. + + Since the code is OK with filter->list fields being NULL, merely check + for filter to be non-NULL (which is the case if nft_cache_update() is + called by nft_cmd_enoent_chain()). + + Fixes: 17297d1acbbf ("cache: Filter chain list on kernel side") + Signed-off-by: Phil Sutter + Reviewed-by: Pablo Neira Ayuso + Tested-by: Eric Garver + +Signed-off-by: Phil Sutter +--- + src/cache.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/cache.c b/src/cache.c +index b82d0fd..5b6f46f 100644 +--- a/src/cache.c ++++ b/src/cache.c +@@ -631,7 +631,7 @@ chain_cache_dump(struct netlink_ctx *ctx, + const char *chain = NULL; + int family = NFPROTO_UNSPEC; + +- if (filter && filter->list.table && filter->list.chain) { ++ if (filter) { + family = filter->list.family; + table = filter->list.table; + chain = filter->list.chain; diff --git a/0283-cache-Include-chains-flowtables-and-objects-in-netli.patch b/0283-cache-Include-chains-flowtables-and-objects-in-netli.patch new file mode 100644 index 0000000..e77a524 --- /dev/null +++ b/0283-cache-Include-chains-flowtables-and-objects-in-netli.patch @@ -0,0 +1,229 @@ +From acd0619c4f9c6e480d68aa1006d38bc3540b8cbe Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:20:05 +0200 +Subject: [PATCH] cache: Include chains, flowtables and objects in netlink + debug output + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 43e82d7e79dc28fd63d5959c397e001a47d7c844 + +commit 43e82d7e79dc28fd63d5959c397e001a47d7c844 +Author: Phil Sutter +Date: Wed Feb 25 20:16:57 2026 +0100 + + cache: Include chains, flowtables and objects in netlink debug output + + In order to test cache filter effectiveness, netlink debug output is + useful as it shows what is actually received from the kernel and maybe + discarded immediately by user space. Therefore add dump calls for these + rule set elements as well. + + While at it, move the netlink_dump_rule() call to an earlier spot, + namely into the nft_mnl_talk() callback to match other netlink dump + calls. + + Signed-off-by: Phil Sutter + Tested-by: Eric Garver + +Signed-off-by: Phil Sutter +--- + src/cache.c | 1 - + src/mnl.c | 60 ++++++++++++++++++++++++++++++++++++++++++++++------- + 2 files changed, 52 insertions(+), 9 deletions(-) + +diff --git a/src/cache.c b/src/cache.c +index 5b6f46f..04a7c94 100644 +--- a/src/cache.c ++++ b/src/cache.c +@@ -711,7 +711,6 @@ static int list_rule_cb(struct nftnl_rule *nlr, void *data) + (h->chain.name && strcmp(chain, h->chain.name) != 0)) + return 0; + +- netlink_dump_rule(nlr, ctx); + rule = netlink_delinearize_rule(ctx, nlr); + assert(rule); + list_add_tail(&rule->list, &ctx->list); +diff --git a/src/mnl.c b/src/mnl.c +index ec423e2..56c3fcf 100644 +--- a/src/mnl.c ++++ b/src/mnl.c +@@ -647,9 +647,15 @@ int mnl_nft_rule_del(struct netlink_ctx *ctx, struct cmd *cmd) + * Rule + */ + ++struct rule_cb_args { ++ struct netlink_ctx *ctx; ++ struct nftnl_rule_list *list; ++}; ++ + static int rule_cb(const struct nlmsghdr *nlh, void *data) + { +- struct nftnl_rule_list *nlr_list = data; ++ struct rule_cb_args *args = data; ++ struct nftnl_rule_list *nlr_list = args->list; + struct nftnl_rule *r; + + if (check_genid(nlh) < 0) +@@ -662,6 +668,8 @@ static int rule_cb(const struct nlmsghdr *nlh, void *data) + if (nftnl_rule_nlmsg_parse(nlh, r) < 0) + goto err_free; + ++ netlink_dump_rule(r, args->ctx); ++ + nftnl_rule_list_add_tail(r, nlr_list); + return MNL_CB_OK; + +@@ -679,6 +687,7 @@ struct nftnl_rule_list *mnl_nft_rule_dump(struct netlink_ctx *ctx, int family, + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nftnl_rule_list *nlr_list; + struct nftnl_rule *nlr = NULL; ++ struct rule_cb_args args; + struct nlmsghdr *nlh; + int msg_type, ret; + +@@ -710,7 +719,9 @@ struct nftnl_rule_list *mnl_nft_rule_dump(struct netlink_ctx *ctx, int family, + nftnl_rule_free(nlr); + } + +- ret = nft_mnl_talk(ctx, nlh, nlh->nlmsg_len, rule_cb, nlr_list); ++ args.list = nlr_list; ++ args.ctx = ctx; ++ ret = nft_mnl_talk(ctx, nlh, nlh->nlmsg_len, rule_cb, &args); + if (ret < 0) + goto err; + +@@ -983,9 +994,15 @@ int mnl_nft_chain_del(struct netlink_ctx *ctx, struct cmd *cmd) + return 0; + } + ++struct chain_cb_args { ++ struct netlink_ctx *ctx; ++ struct nftnl_chain_list *list; ++}; ++ + static int chain_cb(const struct nlmsghdr *nlh, void *data) + { +- struct nftnl_chain_list *nlc_list = data; ++ struct chain_cb_args *args = data; ++ struct nftnl_chain_list *nlc_list = args->list; + struct nftnl_chain *c; + + if (check_genid(nlh) < 0) +@@ -998,6 +1015,8 @@ static int chain_cb(const struct nlmsghdr *nlh, void *data) + if (nftnl_chain_nlmsg_parse(nlh, c) < 0) + goto err_free; + ++ netlink_dump_chain(c, args->ctx); ++ + nftnl_chain_list_add_tail(c, nlc_list); + return MNL_CB_OK; + +@@ -1013,6 +1032,7 @@ struct nftnl_chain_list *mnl_nft_chain_dump(struct netlink_ctx *ctx, + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nftnl_chain_list *nlc_list; + struct nftnl_chain *nlc = NULL; ++ struct chain_cb_args args; + struct nlmsghdr *nlh; + int ret; + +@@ -1036,7 +1056,9 @@ struct nftnl_chain_list *mnl_nft_chain_dump(struct netlink_ctx *ctx, + nftnl_chain_free(nlc); + } + +- ret = nft_mnl_talk(ctx, nlh, nlh->nlmsg_len, chain_cb, nlc_list); ++ args.list = nlc_list; ++ args.ctx = ctx; ++ ret = nft_mnl_talk(ctx, nlh, nlh->nlmsg_len, chain_cb, &args); + if (ret < 0 && errno != ENOENT) + goto err; + +@@ -1614,9 +1636,15 @@ int mnl_nft_obj_del(struct netlink_ctx *ctx, struct cmd *cmd, int type) + return 0; + } + ++struct obj_cb_args { ++ struct netlink_ctx *ctx; ++ struct nftnl_obj_list *list; ++}; ++ + static int obj_cb(const struct nlmsghdr *nlh, void *data) + { +- struct nftnl_obj_list *nln_list = data; ++ struct obj_cb_args *args = data; ++ struct nftnl_obj_list *nln_list = args->list; + struct nftnl_obj *n; + + if (check_genid(nlh) < 0) +@@ -1629,6 +1657,8 @@ static int obj_cb(const struct nlmsghdr *nlh, void *data) + if (nftnl_obj_nlmsg_parse(nlh, n) < 0) + goto err_free; + ++ netlink_dump_obj(n, args->ctx); ++ + nftnl_obj_list_add_tail(n, nln_list); + return MNL_CB_OK; + +@@ -1646,6 +1676,7 @@ mnl_nft_obj_dump(struct netlink_ctx *ctx, int family, + uint16_t nl_flags = dump ? NLM_F_DUMP : NLM_F_ACK; + struct nftnl_obj_list *nln_list; + char buf[MNL_SOCKET_BUFFER_SIZE]; ++ struct obj_cb_args args; + struct nlmsghdr *nlh; + struct nftnl_obj *n; + int msg_type, ret; +@@ -1674,7 +1705,9 @@ mnl_nft_obj_dump(struct netlink_ctx *ctx, int family, + if (nln_list == NULL) + memory_allocation_error(); + +- ret = nft_mnl_talk(ctx, nlh, nlh->nlmsg_len, obj_cb, nln_list); ++ args.list = nln_list; ++ args.ctx = ctx; ++ ret = nft_mnl_talk(ctx, nlh, nlh->nlmsg_len, obj_cb, &args); + if (ret < 0) + goto err; + +@@ -1950,9 +1983,15 @@ int mnl_nft_setelem_get(struct netlink_ctx *ctx, struct nftnl_set *nls, + return nft_mnl_talk(ctx, nlh, nlh->nlmsg_len, set_elem_cb, nls); + } + ++struct flowtable_cb_args { ++ struct netlink_ctx *ctx; ++ struct nftnl_flowtable_list *list; ++}; ++ + static int flowtable_cb(const struct nlmsghdr *nlh, void *data) + { +- struct nftnl_flowtable_list *nln_list = data; ++ struct flowtable_cb_args *args = data; ++ struct nftnl_flowtable_list *nln_list = args->list; + struct nftnl_flowtable *n; + + if (check_genid(nlh) < 0) +@@ -1965,6 +2004,8 @@ static int flowtable_cb(const struct nlmsghdr *nlh, void *data) + if (nftnl_flowtable_nlmsg_parse(nlh, n) < 0) + goto err_free; + ++ netlink_dump_flowtable(n, args->ctx); ++ + nftnl_flowtable_list_add_tail(n, nln_list); + return MNL_CB_OK; + +@@ -1979,6 +2020,7 @@ mnl_nft_flowtable_dump(struct netlink_ctx *ctx, int family, + { + struct nftnl_flowtable_list *nln_list; + char buf[MNL_SOCKET_BUFFER_SIZE]; ++ struct flowtable_cb_args args; + struct nftnl_flowtable *n; + int flags = NLM_F_DUMP; + struct nlmsghdr *nlh; +@@ -2003,7 +2045,9 @@ mnl_nft_flowtable_dump(struct netlink_ctx *ctx, int family, + if (nln_list == NULL) + memory_allocation_error(); + +- ret = nft_mnl_talk(ctx, nlh, nlh->nlmsg_len, flowtable_cb, nln_list); ++ args.list = nln_list; ++ args.ctx = ctx; ++ ret = nft_mnl_talk(ctx, nlh, nlh->nlmsg_len, flowtable_cb, &args); + if (ret < 0 && errno != ENOENT) + goto err; + diff --git a/0284-cache-Respect-family-in-all-list-commands.patch b/0284-cache-Respect-family-in-all-list-commands.patch new file mode 100644 index 0000000..643aa19 --- /dev/null +++ b/0284-cache-Respect-family-in-all-list-commands.patch @@ -0,0 +1,121 @@ +From 5eaeb15b4fc83fe6058ea853a3cc410349934104 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:20:05 +0200 +Subject: [PATCH] cache: Respect family in all list commands + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit f4a5334f503dc9d99bce45020c105b6812dd15f0 + +commit f4a5334f503dc9d99bce45020c105b6812dd15f0 +Author: Phil Sutter +Date: Sat Feb 14 14:58:03 2026 +0100 + + cache: Respect family in all list commands + + Some list commands did not set filter->list.family even if one was given + on command line, fix this. + + Fixes: b3ed8fd8c9f33 ("cache: missing family in cache filtering") + Signed-off-by: Phil Sutter + Reviewed-by: Pablo Neira Ayuso + Tested-by: Eric Garver + +Signed-off-by: Phil Sutter +--- + src/cache.c | 6 ++- + tests/shell/testcases/listing/cache_filters | 46 +++++++++++++++++++++ + 2 files changed, 51 insertions(+), 1 deletion(-) + create mode 100755 tests/shell/testcases/listing/cache_filters + +diff --git a/src/cache.c b/src/cache.c +index 04a7c94..2f3a0f6 100644 +--- a/src/cache.c ++++ b/src/cache.c +@@ -252,10 +252,12 @@ static unsigned int evaluate_cache_list(struct nft_ctx *nft, struct cmd *cmd, + flags |= NFT_CACHE_FULL; + break; + case CMD_OBJ_CHAINS: ++ filter->list.family = cmd->handle.family; + flags |= NFT_CACHE_TABLE | NFT_CACHE_CHAIN; + break; + case CMD_OBJ_SETS: + case CMD_OBJ_MAPS: ++ filter->list.family = cmd->handle.family; + flags |= NFT_CACHE_TABLE | NFT_CACHE_SET; + if (!nft_output_terse(&nft->output)) + flags |= NFT_CACHE_SETELEM; +@@ -263,12 +265,12 @@ static unsigned int evaluate_cache_list(struct nft_ctx *nft, struct cmd *cmd, + case CMD_OBJ_FLOWTABLE: + if (cmd->handle.table.name && + cmd->handle.flowtable.name) { +- filter->list.family = cmd->handle.family; + filter->list.table = cmd->handle.table.name; + filter->list.ft = cmd->handle.flowtable.name; + } + /* fall through */ + case CMD_OBJ_FLOWTABLES: ++ filter->list.family = cmd->handle.family; + flags |= NFT_CACHE_TABLE | NFT_CACHE_FLOWTABLE; + break; + case CMD_OBJ_COUNTER: +@@ -303,6 +305,8 @@ static unsigned int evaluate_cache_list(struct nft_ctx *nft, struct cmd *cmd, + obj_filter_setup(cmd, &flags, filter, NFT_OBJECT_SYNPROXY); + break; + case CMD_OBJ_RULESET: ++ filter->list.family = cmd->handle.family; ++ /* fall through */ + default: + flags |= NFT_CACHE_FULL; + break; +diff --git a/tests/shell/testcases/listing/cache_filters b/tests/shell/testcases/listing/cache_filters +new file mode 100755 +index 0000000..37c8f84 +--- /dev/null ++++ b/tests/shell/testcases/listing/cache_filters +@@ -0,0 +1,46 @@ ++#!/bin/bash ++ ++set -e ++ ++fail() { ++ echo "$*" ++ exit 1 ++} ++ ++$NFT -f - < +Date: Fri, 17 Jul 2026 11:20:05 +0200 +Subject: [PATCH] cache: Filter for table when listing sets or maps + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit b7225cda2a4388f38293fba281fc001dff70a16e + +commit b7225cda2a4388f38293fba281fc001dff70a16e +Author: Phil Sutter +Date: Sat Feb 14 15:02:04 2026 +0100 + + cache: Filter for table when listing sets or maps + + Respect an optionally specified table name to filter listed sets or maps + to by populating the filter accordingly. + + Fixes: a1a6b0a5c3c4 ("cache: finer grain cache population for list commands") + Signed-off-by: Phil Sutter + Reviewed-by: Pablo Neira Ayuso + Tested-by: Eric Garver + +Signed-off-by: Phil Sutter +--- + src/cache.c | 1 + + tests/shell/testcases/listing/cache_filters | 4 ++++ + 2 files changed, 5 insertions(+) + +diff --git a/src/cache.c b/src/cache.c +index 2f3a0f6..b009243 100644 +--- a/src/cache.c ++++ b/src/cache.c +@@ -258,6 +258,7 @@ static unsigned int evaluate_cache_list(struct nft_ctx *nft, struct cmd *cmd, + case CMD_OBJ_SETS: + case CMD_OBJ_MAPS: + filter->list.family = cmd->handle.family; ++ filter->list.table = cmd->handle.table.name; + flags |= NFT_CACHE_TABLE | NFT_CACHE_SET; + if (!nft_output_terse(&nft->output)) + flags |= NFT_CACHE_SETELEM; +diff --git a/tests/shell/testcases/listing/cache_filters b/tests/shell/testcases/listing/cache_filters +index 37c8f84..7a89330 100755 +--- a/tests/shell/testcases/listing/cache_filters ++++ b/tests/shell/testcases/listing/cache_filters +@@ -22,6 +22,8 @@ table ip ip_t { + chain ip_t_c2 { + } + } ++table ip ip_t2 { ++} + EOF + + $NFT --debug=netlink list ruleset | \ +@@ -38,6 +40,8 @@ $NFT --debug=netlink list sets | \ + grep -q 'family 2 ip_t_s ip_t' || fail "broken list sets" + $NFT --debug=netlink list sets ip6 | \ + grep -q 'family 2 ip_t_s ip_t' && fail "broken list sets family filter" ++$NFT --debug=netlink list sets ip ip_t2 | \ ++ grep -q 'family 2 ip_t_s ip_t' && fail "broken list sets table filter" + + $NFT --debug=netlink list flowtables | \ + grep -q 'flow table ip_t ip_t_ft' || fail "broken list flowtables" diff --git a/0286-cache-Filter-for-table-when-listing-flowtables.patch b/0286-cache-Filter-for-table-when-listing-flowtables.patch new file mode 100644 index 0000000..c5446ca --- /dev/null +++ b/0286-cache-Filter-for-table-when-listing-flowtables.patch @@ -0,0 +1,52 @@ +From 2836fbfd0daceb4879fa3d502b03e3e4eac8eddf Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:20:05 +0200 +Subject: [PATCH] cache: Filter for table when listing flowtables + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit fa5d7fba4bcc9b7c5c9ec37bbe8d3df3f8298745 + +commit fa5d7fba4bcc9b7c5c9ec37bbe8d3df3f8298745 +Author: Phil Sutter +Date: Wed Feb 25 21:07:32 2026 +0100 + + cache: Filter for table when listing flowtables + + Respect an optionally specified table name to filter listed flowtables + to by populating the filter accordingly. + + Fixes: a1a6b0a5c3c4 ("cache: finer grain cache population for list commands") + Signed-off-by: Phil Sutter + Reviewed-by: Pablo Neira Ayuso + Tested-by: Eric Garver + +Signed-off-by: Phil Sutter +--- + src/cache.c | 1 + + tests/shell/testcases/listing/cache_filters | 3 +++ + 2 files changed, 4 insertions(+) + +diff --git a/src/cache.c b/src/cache.c +index b009243..cac6421 100644 +--- a/src/cache.c ++++ b/src/cache.c +@@ -272,6 +272,7 @@ static unsigned int evaluate_cache_list(struct nft_ctx *nft, struct cmd *cmd, + /* fall through */ + case CMD_OBJ_FLOWTABLES: + filter->list.family = cmd->handle.family; ++ filter->list.table = cmd->handle.table.name; + flags |= NFT_CACHE_TABLE | NFT_CACHE_FLOWTABLE; + break; + case CMD_OBJ_COUNTER: +diff --git a/tests/shell/testcases/listing/cache_filters b/tests/shell/testcases/listing/cache_filters +index 7a89330..e3d0e5e 100755 +--- a/tests/shell/testcases/listing/cache_filters ++++ b/tests/shell/testcases/listing/cache_filters +@@ -47,4 +47,7 @@ $NFT --debug=netlink list flowtables | \ + grep -q 'flow table ip_t ip_t_ft' || fail "broken list flowtables" + $NFT --debug=netlink list flowtables ip6 | \ + grep -q 'flow table ip_t ip_t_ft' && fail "broken list flowtables family filter" ++$NFT --debug=netlink list flowtables ip ip_t2 | \ ++ grep -q 'flow table ip_t ip_t_ft' && fail "broken list flowtables table filter" ++ + exit 0 diff --git a/0287-segtree-Fix-for-variable-sized-object-may-not-be-ini.patch b/0287-segtree-Fix-for-variable-sized-object-may-not-be-ini.patch new file mode 100644 index 0000000..f79345f --- /dev/null +++ b/0287-segtree-Fix-for-variable-sized-object-may-not-be-ini.patch @@ -0,0 +1,42 @@ +From 490ad53a45cf8ebc2635486a3620a5dfa3f6c29a Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:20:05 +0200 +Subject: [PATCH] segtree: Fix for variable-sized object may not be initialized + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 96ba00889f9d4932d8bc2b08461f05793a9a2ac4 + +commit 96ba00889f9d4932d8bc2b08461f05793a9a2ac4 +Author: Phil Sutter +Date: Wed Mar 18 22:05:34 2026 +0100 + + segtree: Fix for variable-sized object may not be initialized + + Seen with gcc-11.5.0 on an aarch64 machine, build failed. Looking at the + code, r1len (or r1->len, actually) really seems variable. So use + memset() and fix build for that older compiler version at least. + + Fixes: e8b17865833b8 ("segtree: Fix range aggregation on Big Endian") + Signed-off-by: Phil Sutter + Acked-by: Florian Westphal + +Signed-off-by: Phil Sutter +--- + src/segtree.c | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/src/segtree.c b/src/segtree.c +index b8ec99c..5924460 100644 +--- a/src/segtree.c ++++ b/src/segtree.c +@@ -411,8 +411,9 @@ void concat_range_aggregate(struct expr *set) + string_type) { + unsigned int r1len = div_round_up(r1->len, BITS_PER_BYTE); + unsigned int str_len = prefix_len / BITS_PER_BYTE; +- char data[r1len + 1] = {}; ++ char data[r1len + 1]; + ++ memset(data, 0, r1len + 1); + mpz_export_data(data, r1->value, BYTEORDER_HOST_ENDIAN, r1len); + data[str_len] = '*'; + diff --git a/0288-mnl-Fix-ordering-of-hooks-in-list-hooks-output.patch b/0288-mnl-Fix-ordering-of-hooks-in-list-hooks-output.patch new file mode 100644 index 0000000..2f380a3 --- /dev/null +++ b/0288-mnl-Fix-ordering-of-hooks-in-list-hooks-output.patch @@ -0,0 +1,39 @@ +From c17011df23b33bbbb5e5711b3c61e411157ec43e Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:20:05 +0200 +Subject: [PATCH] mnl: Fix ordering of hooks in 'list hooks' output + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 2f3b8d37c2e46f2ddf086c50affcd224b1f7f7dd + +commit 2f3b8d37c2e46f2ddf086c50affcd224b1f7f7dd +Author: Phil Sutter +Date: Fri Mar 20 13:30:52 2026 +0100 + + mnl: Fix ordering of hooks in 'list hooks' output + + Hooks with same family, basehook and priority were inadvertently + inserted into the list in reverse ordering, fix that. + + Suggested-by: Florian Westphal + Fixes: b98fee20bfe23 ("mnl: revisit hook listing") + Signed-off-by: Phil Sutter + +Signed-off-by: Phil Sutter +--- + src/mnl.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/mnl.c b/src/mnl.c +index 56c3fcf..28c8403 100644 +--- a/src/mnl.c ++++ b/src/mnl.c +@@ -2257,7 +2257,7 @@ static void basehook_list_add_tail(struct basehook *b, struct list_head *head) + continue; + if (hook->num != b->num) + continue; +- if (hook->prio < b->prio) ++ if (hook->prio <= b->prio) + continue; + + list_add(&b->list, &hook->list); diff --git a/0289-tests-py-don-t-use-a-fixed-filename.patch b/0289-tests-py-don-t-use-a-fixed-filename.patch new file mode 100644 index 0000000..124a96b --- /dev/null +++ b/0289-tests-py-don-t-use-a-fixed-filename.patch @@ -0,0 +1,85 @@ +From d75d2148a093b038b993aa79f3e11bddf3a61ed0 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:20:06 +0200 +Subject: [PATCH] tests: py: don't use a fixed filename + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit f2b5d6fc26b9aa94eddb065628858e79069b5012 + +commit f2b5d6fc26b9aa94eddb065628858e79069b5012 +Author: Florian Westphal +Date: Thu Mar 5 23:52:48 2026 +0100 + + tests: py: don't use a fixed filename + + Using a predicatable filename in /tmp is not good practice. + + This test runs with uid 0 and stray symlink could lead to unwanted + effects. Use a temporary file and auto-delete it unless -k/--keep gets + passed to us. + + Signed-off-by: Florian Westphal + +Signed-off-by: Phil Sutter +--- + tests/py/nft-test.py | 19 +++++++++++++++---- + 1 file changed, 15 insertions(+), 4 deletions(-) + +diff --git a/tests/py/nft-test.py b/tests/py/nft-test.py +index b398c50..581a4f2 100755 +--- a/tests/py/nft-test.py ++++ b/tests/py/nft-test.py +@@ -30,13 +30,13 @@ os.environ['TZ'] = 'UTC-2' + from nftables import Nftables + + TESTS_DIRECTORY = ["any", "arp", "bridge", "inet", "ip", "ip6", "netdev"] +-LOGFILE = "/tmp/nftables-test.log" + log_file = None + table_list = [] + chain_list = [] + all_set = dict() + obj_list = [] + signal_received = 0 ++auto_delete = True + + + class Colors: +@@ -1513,6 +1513,9 @@ def main(): + parser.add_argument('-l', '--library', default=None, + help='path to libntables.so.1, overrides --host') + ++ parser.add_argument('-k', '--keep', default=False, ++ help='keep log file around after tests') ++ + parser.add_argument('-N', '--no-netns', action='store_true', + dest='no_netns', + help='Do not run in own network namespace') +@@ -1560,6 +1563,11 @@ def main(): + "You need to build the project." % args.library) + return 99 + ++ global auto_delete ++ ++ if args.keep: ++ auto_delete = False ++ + if args.enable_schema and not args.enable_json: + print_error("Option --schema requires option --json") + return 99 +@@ -1571,10 +1579,13 @@ def main(): + tests = passed = warnings = errors = 0 + global log_file + try: +- log_file = open(LOGFILE, 'w') +- print_info("Log will be available at %s" % LOGFILE) ++ log_file = tempfile.NamedTemporaryFile(prefix="nftables-test-py-", suffix=".log", mode='w', delete=auto_delete) ++ if auto_delete: ++ print_info("Log file %s will not be retained. Pass -k to keep it.") ++ else: ++ print_info("Log will be available at %s" % log_file.name) + except IOError: +- print_error("Cannot open log file %s" % LOGFILE) ++ print_error("Cannot create a temporary log file") + return 99 + + file_list = [] diff --git a/0290-tests-py-print-the-file-name-as-intended.patch b/0290-tests-py-print-the-file-name-as-intended.patch new file mode 100644 index 0000000..0de75ed --- /dev/null +++ b/0290-tests-py-print-the-file-name-as-intended.patch @@ -0,0 +1,35 @@ +From 845bba4d86d62afe9d52aeece4dc56886a4292f6 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:20:06 +0200 +Subject: [PATCH] tests: py: print the file name as intended + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit fbd69788d961189430a9587133b5a4e217c8d4f1 + +commit fbd69788d961189430a9587133b5a4e217c8d4f1 +Author: Florian Westphal +Date: Thu Apr 16 19:01:33 2026 +0200 + + tests: py: print the file name as intended + + Fixes: f2b5d6fc26b9 ("tests: py: don't use a fixed filename") + Signed-off-by: Florian Westphal + +Signed-off-by: Phil Sutter +--- + tests/py/nft-test.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/tests/py/nft-test.py b/tests/py/nft-test.py +index 581a4f2..8ae65b4 100755 +--- a/tests/py/nft-test.py ++++ b/tests/py/nft-test.py +@@ -1581,7 +1581,7 @@ def main(): + try: + log_file = tempfile.NamedTemporaryFile(prefix="nftables-test-py-", suffix=".log", mode='w', delete=auto_delete) + if auto_delete: +- print_info("Log file %s will not be retained. Pass -k to keep it.") ++ print_info("Log file %s will not be retained. Pass -k to keep it." % log_file.name) + else: + print_info("Log will be available at %s" % log_file.name) + except IOError: diff --git a/0291-tests-py-osf-is-ip-only.patch b/0291-tests-py-osf-is-ip-only.patch new file mode 100644 index 0000000..e29e410 --- /dev/null +++ b/0291-tests-py-osf-is-ip-only.patch @@ -0,0 +1,134 @@ +From 758237aabee466c34538f74317076e4c3a26db6e Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:20:06 +0200 +Subject: [PATCH] tests: py: osf is ip-only + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 5b70e8efff75b5d4d04b6fdb56d8fe1924aaba0e +Conflicts: Adjust to missing commit 0f566ac1c1dbd + ("tests: py: Update payload records") + +commit 5b70e8efff75b5d4d04b6fdb56d8fe1924aaba0e +Author: Florian Westphal +Date: Wed Apr 22 19:22:50 2026 +0200 + + tests: py: osf is ip-only + + Pending kernel patch rejects osf in inet/ip6, move this to ip to + prevent CI failure. + + Signed-off-by: Florian Westphal + +Signed-off-by: Phil Sutter +--- + tests/py/{inet => ip}/osf.t | 2 -- + tests/py/{inet => ip}/osf.t.json | 0 + tests/py/{inet => ip}/osf.t.json.output | 0 + tests/py/{inet => ip}/osf.t.payload | 32 ++++++++++++------------- + 4 files changed, 16 insertions(+), 18 deletions(-) + rename tests/py/{inet => ip}/osf.t (92%) + rename tests/py/{inet => ip}/osf.t.json (100%) + rename tests/py/{inet => ip}/osf.t.json.output (100%) + rename tests/py/{inet => ip}/osf.t.payload (80%) + +diff --git a/tests/py/inet/osf.t b/tests/py/ip/osf.t +similarity index 92% +rename from tests/py/inet/osf.t +rename to tests/py/ip/osf.t +index c828541..368baef 100644 +--- a/tests/py/inet/osf.t ++++ b/tests/py/ip/osf.t +@@ -1,8 +1,6 @@ + :osfchain;type filter hook input priority 0 + + *ip;osfip;osfchain +-*ip6;osfip6;osfchain +-*inet;osfinet;osfchain + + osf name "Linux";ok + osf ttl loose name "Linux";ok +diff --git a/tests/py/inet/osf.t.json b/tests/py/ip/osf.t.json +similarity index 100% +rename from tests/py/inet/osf.t.json +rename to tests/py/ip/osf.t.json +diff --git a/tests/py/inet/osf.t.json.output b/tests/py/ip/osf.t.json.output +similarity index 100% +rename from tests/py/inet/osf.t.json.output +rename to tests/py/ip/osf.t.json.output +diff --git a/tests/py/inet/osf.t.payload b/tests/py/ip/osf.t.payload +similarity index 80% +rename from tests/py/inet/osf.t.payload +rename to tests/py/ip/osf.t.payload +index 6ddab97..9bb9e47 100644 +--- a/tests/py/inet/osf.t.payload ++++ b/tests/py/ip/osf.t.payload +@@ -1,53 +1,53 @@ + # osf name "Linux" +-inet osfinet osfchain ++ip osfip osfchain + [ osf dreg 1 ] + [ cmp eq reg 1 0x756e694c 0x00000078 0x00000000 0x00000000 ] + + # osf ttl loose name "Linux" +-inet osfinet osfchain ++ip osfip osfchain + [ osf dreg 1 ] + [ cmp eq reg 1 0x756e694c 0x00000078 0x00000000 0x00000000 ] + + # osf ttl skip name "Linux" +-inet osfinet osfchain ++ip osfip osfchain + [ osf dreg 1 ] + [ cmp eq reg 1 0x756e694c 0x00000078 0x00000000 0x00000000 ] + + # osf ttl skip version "Linux:3.0" +-inet osfinet osfchain ++ip osfip osfchain + [ osf dreg 1 ] + [ cmp eq reg 1 0x756e694c 0x2e333a78 0x00000030 0x00000000 ] + + # osf name { "Windows", "MacOs" } +-__set%d osfinet 3 size 2 +-__set%d osfinet 0 ++__set%d osfip 3 size 2 ++__set%d osfip 0 + element 646e6957 0073776f 00000000 00000000 : 0 [end] element 4f63614d 00000073 00000000 00000000 : 0 [end] +-inet osfinet osfchain ++ip osfip osfchain + [ osf dreg 1 ] + [ lookup reg 1 set __set%d ] + + # osf version { "Windows:XP", "MacOs:Sierra" } +-__set%d osfinet 3 size 2 +-__set%d osfinet 0 ++__set%d osfip 3 size 2 ++__set%d osfip 0 + element 646e6957 3a73776f 00005058 00000000 : 0 [end] element 4f63614d 69533a73 61727265 00000000 : 0 [end] +-inet osfinet osfchain ++ip osfip osfchain + [ osf dreg 1 ] + [ lookup reg 1 set __set%d ] + + # ct mark set osf name map { "Windows" : 0x00000001, "MacOs" : 0x00000002 } +-__map%d osfinet b size 2 +-__map%d osfinet 0 ++__map%d osfip b size 2 ++__map%d osfip 0 + element 646e6957 0073776f 00000000 00000000 : 00000001 0 [end] element 4f63614d 00000073 00000000 00000000 : 00000002 0 [end] +-inet osfinet osfchain ++ip osfip osfchain + [ osf dreg 1 ] + [ lookup reg 1 set __map%d dreg 1 ] + [ ct set mark with reg 1 ] + + # ct mark set osf version map { "Windows:XP" : 0x00000003, "MacOs:Sierra" : 0x00000004 } +-__map%d osfinet b size 2 +-__map%d osfinet 0 ++__map%d osfip b size 2 ++__map%d osfip 0 + element 646e6957 3a73776f 00005058 00000000 : 00000003 0 [end] element 4f63614d 69533a73 61727265 00000000 : 00000004 0 [end] +-inet osfinet osfchain ++ip osfip osfchain + [ osf dreg 1 ] + [ lookup reg 1 set __map%d dreg 1 ] + [ ct set mark with reg 1 ] diff --git a/0292-cache-honor-c-check-for-reset-commands.patch b/0292-cache-honor-c-check-for-reset-commands.patch new file mode 100644 index 0000000..abf7e3a --- /dev/null +++ b/0292-cache-honor-c-check-for-reset-commands.patch @@ -0,0 +1,52 @@ +From 27fad9ad7d048138ec595a197d9408d5cf3b7195 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:20:22 +0200 +Subject: [PATCH] cache: honor -c/--check for reset commands + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 25f2ff13cf6bc84a16febddcf8c3eda2018c7ece + +commit 25f2ff13cf6bc84a16febddcf8c3eda2018c7ece +Author: Pablo Neira Ayuso +Date: Mon May 11 21:51:43 2026 +0200 + + cache: honor -c/--check for reset commands + + Currently: + + nft -c reset rules ip x + + ignores -c/--check. + + The reset and list commands use the netlink GET/DUMP nfnetlink API which + provides no check semantics, compared to the NEW/DELETE nfnetlink batch + API which indeed does. + + Emulate -c/--check for the reset command by handling this as a list + command, so the state of the objects is just listed, not reset. This + allows to check for presence and dump the content of the objects. + + Fixes: dbff26bfba83 ("cache: consolidate reset command") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/cache.c | 5 ++++- + 1 file changed, 4 insertions(+), 1 deletion(-) + +diff --git a/src/cache.c b/src/cache.c +index cac6421..c33811a 100644 +--- a/src/cache.c ++++ b/src/cache.c +@@ -522,7 +522,10 @@ int nft_cache_evaluate(struct nft_ctx *nft, struct list_head *cmds, + flags = evaluate_cache_get(cmd, flags); + break; + case CMD_RESET: +- flags = evaluate_cache_reset(cmd, flags, filter); ++ if (nft->check) ++ flags = evaluate_cache_list(nft, cmd, flags, filter); ++ else ++ flags = evaluate_cache_reset(cmd, flags, filter); + break; + case CMD_LIST: + flags = evaluate_cache_list(nft, cmd, flags, filter); diff --git a/0293-parser_json-fix-map-set-type-confusion-crash-in-map-.patch b/0293-parser_json-fix-map-set-type-confusion-crash-in-map-.patch new file mode 100644 index 0000000..c9f1bae --- /dev/null +++ b/0293-parser_json-fix-map-set-type-confusion-crash-in-map-.patch @@ -0,0 +1,50 @@ +From d50a7ee8dc8bfee82934456be1d9ce13ace1b1f9 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:20:22 +0200 +Subject: [PATCH] parser_json: fix map/set type confusion crash in map + statement parser + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 628701e796ab9a4da5541cf77fa809c2ce8e7720 + +commit 628701e796ab9a4da5541cf77fa809c2ce8e7720 +Author: Florian Westphal +Date: Thu May 21 16:18:55 2026 +0200 + + parser_json: fix map/set type confusion crash in map statement parser + + Quoting Alessandro Gario: + Anthropic is conducting research into the use of large language models + for automated vulnerability discovery in open source software. As part + of that work, Anthropic used Claude to scan a set of widely used open + source projects for security issues. Anthropic then engaged Trail of + Bits to independently triage, manually validate, and develop patches + for the findings. [..] + + json_parse_map_stmt() in src/parser_json.c:2584 passes + &stmt->set.stmt_list to json_parse_set_stmt_list(), but the + statement object is a map_stmt. The function reads as a copy/edit of + json_parse_set_stmt() that forgot to retarget the list-head pointer. + + Reported-by: Alessandro Gario + Fixes: 27a2da23d508 ("netlink_linearize: skip set element expression in map statement key") + Signed-off-by: Florian Westphal + +Signed-off-by: Phil Sutter +--- + src/parser_json.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/parser_json.c b/src/parser_json.c +index 61427ca..8a8576f 100644 +--- a/src/parser_json.c ++++ b/src/parser_json.c +@@ -2477,7 +2477,7 @@ static struct stmt *json_parse_map_stmt(struct json_ctx *ctx, + stmt->map.set = expr2; + + if (!json_unpack(value, "{s:o}", "stmt", &stmt_json) && +- json_parse_set_stmt_list(ctx, &stmt->set.stmt_list, stmt_json) < 0) { ++ json_parse_set_stmt_list(ctx, &stmt->map.stmt_list, stmt_json) < 0) { + stmt_free(stmt); + return NULL; + } diff --git a/0294-segtree-rename-set_elem_add-to-set_elem_expr_add.patch b/0294-segtree-rename-set_elem_add-to-set_elem_expr_add.patch new file mode 100644 index 0000000..6bfe349 --- /dev/null +++ b/0294-segtree-rename-set_elem_add-to-set_elem_expr_add.patch @@ -0,0 +1,68 @@ +From 3dd8576fc70ed42becb6ec748e5ebee38f275d25 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:20:22 +0200 +Subject: [PATCH] segtree: rename set_elem_add() to set_elem_expr_add() + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit f598264de4bb8f8366e9d93ee2f05bd31d020728 + +commit f598264de4bb8f8366e9d93ee2f05bd31d020728 +Author: Pablo Neira Ayuso +Date: Thu Feb 5 03:41:15 2026 +0100 + + segtree: rename set_elem_add() to set_elem_expr_add() + + Just a clean up, to prepare for the introduction of struct set_elem + that will provide a set_elem_add() function again. + + Rename it now to leave room for such future change. + + Signed-off-by: Pablo Neira Ayuso + Signed-off-by: Florian Westphal + +Signed-off-by: Phil Sutter +--- + src/segtree.c | 14 +++++++------- + 1 file changed, 7 insertions(+), 7 deletions(-) + +diff --git a/src/segtree.c b/src/segtree.c +index 5924460..3cdecfc 100644 +--- a/src/segtree.c ++++ b/src/segtree.c +@@ -53,8 +53,8 @@ static void interval_expr_copy(struct expr *dst, struct expr *src) + list_splice_init(&src->stmt_list, &dst->stmt_list); + } + +-static void set_elem_add(const struct set *set, struct expr *init, mpz_t value, +- uint32_t flags, enum byteorder byteorder) ++static void set_elem_expr_add(const struct set *set, struct expr *init, ++ mpz_t value, uint32_t flags, enum byteorder byteorder) + { + struct expr *expr; + +@@ -82,8 +82,8 @@ struct expr *get_set_intervals(const struct set *set, const struct expr *init) + list_for_each_entry(i, &init->expressions, list) { + switch (i->key->etype) { + case EXPR_VALUE: +- set_elem_add(set, new_init, i->key->value, +- i->flags, byteorder); ++ set_elem_expr_add(set, new_init, i->key->value, ++ i->flags, byteorder); + break; + case EXPR_CONCAT: + compound_expr_add(new_init, expr_clone(i)); +@@ -95,11 +95,11 @@ struct expr *get_set_intervals(const struct set *set, const struct expr *init) + break; + default: + range_expr_value_low(low, i); +- set_elem_add(set, new_init, low, 0, i->byteorder); ++ set_elem_expr_add(set, new_init, low, 0, i->byteorder); + range_expr_value_high(high, i); + mpz_add_ui(high, high, 1); +- set_elem_add(set, new_init, high, +- EXPR_F_INTERVAL_END, i->byteorder); ++ set_elem_expr_add(set, new_init, high, ++ EXPR_F_INTERVAL_END, i->byteorder); + break; + } + } diff --git a/0295-src-remove-EXPR_SET_ELEM-in-range_expr_value_-low-hi.patch b/0295-src-remove-EXPR_SET_ELEM-in-range_expr_value_-low-hi.patch new file mode 100644 index 0000000..cea4c37 --- /dev/null +++ b/0295-src-remove-EXPR_SET_ELEM-in-range_expr_value_-low-hi.patch @@ -0,0 +1,118 @@ +From 659ffd42a84dba638b913a615f35a8fbae37e960 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:20:22 +0200 +Subject: [PATCH] src: remove EXPR_SET_ELEM in range_expr_value_{low,high}() + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 33e759b0d1f6369c7e82ca528a81a32928ac9483 + +commit 33e759b0d1f6369c7e82ca528a81a32928ac9483 +Author: Pablo Neira Ayuso +Date: Thu Feb 5 03:41:17 2026 +0100 + + src: remove EXPR_SET_ELEM in range_expr_value_{low,high}() + + Call range_expr_value_{low,high}() with the key instead to skip one + level of indirection. + + This is to prepare for the future removal of EXPR_SET_ELEM. + + Signed-off-by: Pablo Neira Ayuso + Signed-off-by: Florian Westphal + +Signed-off-by: Phil Sutter +--- + src/expression.c | 4 ---- + src/intervals.c | 12 ++++++------ + src/segtree.c | 8 ++++---- + 3 files changed, 10 insertions(+), 14 deletions(-) + +diff --git a/src/expression.c b/src/expression.c +index 781d689..da3075e 100644 +--- a/src/expression.c ++++ b/src/expression.c +@@ -1585,8 +1585,6 @@ void range_expr_value_low(mpz_t rop, const struct expr *expr) + return range_expr_value_low(rop, expr->left); + case EXPR_MAPPING: + return range_expr_value_low(rop, expr->left); +- case EXPR_SET_ELEM: +- return range_expr_value_low(rop, expr->key); + default: + BUG("invalid range expression type %s\n", expr_name(expr)); + } +@@ -1612,8 +1610,6 @@ void range_expr_value_high(mpz_t rop, const struct expr *expr) + return range_expr_value_high(rop, expr->right); + case EXPR_MAPPING: + return range_expr_value_high(rop, expr->left); +- case EXPR_SET_ELEM: +- return range_expr_value_high(rop, expr->key); + default: + BUG("invalid range expression type %s\n", expr_name(expr)); + } +diff --git a/src/intervals.c b/src/intervals.c +index 31adb82..83cfbe3 100644 +--- a/src/intervals.c ++++ b/src/intervals.c +@@ -175,8 +175,8 @@ static void setelem_automerge(struct set_automerge_ctx *ctx) + if (i->key->etype == EXPR_SET_ELEM_CATCHALL) + continue; + +- range_expr_value_low(range.low, i); +- range_expr_value_high(range.high, i); ++ range_expr_value_low(range.low, i->key); ++ range_expr_value_high(range.high, i->key); + + if (!prev || + interval_expr_key(prev)->timeout != interval_expr_key(i)->timeout || +@@ -414,8 +414,8 @@ static int setelem_delete(struct list_head *msgs, struct set *set, + mpz_bitmask(range.low, i->len); + mpz_bitmask(range.high, i->len); + } else { +- range_expr_value_low(range.low, i); +- range_expr_value_high(range.high, i); ++ range_expr_value_low(range.low, i->key); ++ range_expr_value_high(range.high, i->key); + } + + if (!prev && elem->flags & EXPR_F_REMOVE) { +@@ -576,8 +576,8 @@ static int setelem_overlap(struct list_head *msgs, struct set *set, + if (i->key->etype == EXPR_SET_ELEM_CATCHALL) + continue; + +- range_expr_value_low(range.low, i); +- range_expr_value_high(range.high, i); ++ range_expr_value_low(range.low, i->key); ++ range_expr_value_high(range.high, i->key); + + if (!prev) { + prev = elem; +diff --git a/src/segtree.c b/src/segtree.c +index 3cdecfc..3f31176 100644 +--- a/src/segtree.c ++++ b/src/segtree.c +@@ -94,9 +94,9 @@ struct expr *get_set_intervals(const struct set *set, const struct expr *init) + compound_expr_add(new_init, expr_clone(i)); + break; + default: +- range_expr_value_low(low, i); ++ range_expr_value_low(low, i->key); + set_elem_expr_add(set, new_init, low, 0, i->byteorder); +- range_expr_value_high(high, i); ++ range_expr_value_high(high, i->key); + mpz_add_ui(high, high, 1); + set_elem_expr_add(set, new_init, high, + EXPR_F_INTERVAL_END, i->byteorder); +@@ -145,11 +145,11 @@ static struct expr *get_set_interval_find(const struct set *cache_set, + /* fall-through */ + case EXPR_PREFIX: + case EXPR_RANGE: +- range_expr_value_low(val, i); ++ range_expr_value_low(val, i->key); + if (left && mpz_cmp(expr_value(left)->value, val)) + break; + +- range_expr_value_high(val, i); ++ range_expr_value_high(val, i->key); + if (right && mpz_cmp(expr_value(right)->value, val)) + break; + diff --git a/0296-segtree-replace-default-case-by-specific-types-in-ge.patch b/0296-segtree-replace-default-case-by-specific-types-in-ge.patch new file mode 100644 index 0000000..85cb02c --- /dev/null +++ b/0296-segtree-replace-default-case-by-specific-types-in-ge.patch @@ -0,0 +1,51 @@ +From 7ea5c5933662529f9c593dc2eb34bda3bbeaf3b3 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:20:22 +0200 +Subject: [PATCH] segtree: replace default case by specific types in + get_set_intervals() + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 0188a0c971059150c7162444a3233d38abf0b843 + +commit 0188a0c971059150c7162444a3233d38abf0b843 +Author: Pablo Neira Ayuso +Date: Thu Feb 5 03:41:25 2026 +0100 + + segtree: replace default case by specific types in get_set_intervals() + + Use specific types, so default case can be left for catching bugs. + + Signed-off-by: Pablo Neira Ayuso + Signed-off-by: Florian Westphal + +Signed-off-by: Phil Sutter +--- + src/segtree.c | 8 +++++++- + 1 file changed, 7 insertions(+), 1 deletion(-) + +diff --git a/src/segtree.c b/src/segtree.c +index 3f31176..2ad3db7 100644 +--- a/src/segtree.c ++++ b/src/segtree.c +@@ -93,7 +93,10 @@ struct expr *get_set_intervals(const struct set *set, const struct expr *init) + case EXPR_SET_ELEM_CATCHALL: + compound_expr_add(new_init, expr_clone(i)); + break; +- default: ++ case EXPR_PREFIX: ++ case EXPR_RANGE: ++ case EXPR_RANGE_VALUE: ++ case EXPR_MAPPING: + range_expr_value_low(low, i->key); + set_elem_expr_add(set, new_init, low, 0, i->byteorder); + range_expr_value_high(high, i->key); +@@ -101,6 +104,9 @@ struct expr *get_set_intervals(const struct set *set, const struct expr *init) + set_elem_expr_add(set, new_init, high, + EXPR_F_INTERVAL_END, i->byteorder); + break; ++ default: ++ BUG("unexpected expression %s", expr_name(i->key)); ++ break; + } + } + diff --git a/0297-segtree-fix-get-element-command-with-open-intervals.patch b/0297-segtree-fix-get-element-command-with-open-intervals.patch new file mode 100644 index 0000000..81313a3 --- /dev/null +++ b/0297-segtree-fix-get-element-command-with-open-intervals.patch @@ -0,0 +1,67 @@ +From 95c0f26b24f3a09ca1bd09cbd395670329721de8 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:20:22 +0200 +Subject: [PATCH] segtree: fix get element command with open intervals + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 4eafc1a2a9ef5a827b1b4e58cb3b2832d2eb1650 + +commit 4eafc1a2a9ef5a827b1b4e58cb3b2832d2eb1650 +Author: Pablo Neira Ayuso +Date: Thu Jul 2 11:57:00 2026 +0200 + + segtree: fix get element command with open intervals + + Skip the closing end element in case this is an open interval. + Otherwise, a bogus end element max(type) + 1 is provided, eg. in + inet_service, this results as a 0x10000 with end interval flag + which is interpreted by the kernel as a matching closing element. + + Fixes: a43cc8d53096 ("src: support for get element command") + Reported-by: Melbin K Mathew + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/segtree.c | 9 ++++++++- + 1 file changed, 8 insertions(+), 1 deletion(-) + +diff --git a/src/segtree.c b/src/segtree.c +index 2ad3db7..c659dbf 100644 +--- a/src/segtree.c ++++ b/src/segtree.c +@@ -70,12 +70,13 @@ static void set_elem_expr_add(const struct set *set, struct expr *init, + struct expr *get_set_intervals(const struct set *set, const struct expr *init) + { + enum byteorder byteorder = get_key_byteorder(set->key); ++ mpz_t low, high, mask; + struct expr *new_init; +- mpz_t low, high; + struct expr *i; + + mpz_init2(low, set->key->len); + mpz_init2(high, set->key->len); ++ mpz_init2(mask, set->key->len); + + new_init = list_expr_alloc(&internal_location); + +@@ -100,6 +101,11 @@ struct expr *get_set_intervals(const struct set *set, const struct expr *init) + range_expr_value_low(low, i->key); + set_elem_expr_add(set, new_init, low, 0, i->byteorder); + range_expr_value_high(high, i->key); ++ mpz_bitmask(mask, i->len); ++ if (set_is_non_concat_range(set) && ++ !mpz_cmp(mask, high)) ++ break; ++ + mpz_add_ui(high, high, 1); + set_elem_expr_add(set, new_init, high, + EXPR_F_INTERVAL_END, i->byteorder); +@@ -110,6 +116,7 @@ struct expr *get_set_intervals(const struct set *set, const struct expr *init) + } + } + ++ mpz_clear(mask); + mpz_clear(low); + mpz_clear(high); + diff --git a/0298-tests-py-Fix-keep-test-runner-option.patch b/0298-tests-py-Fix-keep-test-runner-option.patch new file mode 100644 index 0000000..46ef6f4 --- /dev/null +++ b/0298-tests-py-Fix-keep-test-runner-option.patch @@ -0,0 +1,72 @@ +From dc52f8c9121550340c2dded5c89deb000cddaf7a Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:20:22 +0200 +Subject: [PATCH] tests: py: Fix --keep test runner option + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 68086402c28a4934d61fb9fdb796bc811fb045ee + +commit 68086402c28a4934d61fb9fdb796bc811fb045ee +Author: Phil Sutter +Date: Thu Jul 16 00:57:56 2026 +0200 + + tests: py: Fix --keep test runner option + + The boolean flag expected a value, although not used. Set + 'action=store_true' to really make it a flag. + + While at it, simplify the code by eliminating the inverse-value + auto_delete helper variable. + + Fixes: f2b5d6fc26b9a ("tests: py: don't use a fixed filename") + Signed-off-by: Phil Sutter + +Signed-off-by: Phil Sutter +--- + tests/py/nft-test.py | 12 +++--------- + 1 file changed, 3 insertions(+), 9 deletions(-) + +diff --git a/tests/py/nft-test.py b/tests/py/nft-test.py +index 8ae65b4..b1bccea 100755 +--- a/tests/py/nft-test.py ++++ b/tests/py/nft-test.py +@@ -36,7 +36,6 @@ chain_list = [] + all_set = dict() + obj_list = [] + signal_received = 0 +-auto_delete = True + + + class Colors: +@@ -1513,7 +1512,7 @@ def main(): + parser.add_argument('-l', '--library', default=None, + help='path to libntables.so.1, overrides --host') + +- parser.add_argument('-k', '--keep', default=False, ++ parser.add_argument('-k', '--keep', action='store_true', + help='keep log file around after tests') + + parser.add_argument('-N', '--no-netns', action='store_true', +@@ -1563,11 +1562,6 @@ def main(): + "You need to build the project." % args.library) + return 99 + +- global auto_delete +- +- if args.keep: +- auto_delete = False +- + if args.enable_schema and not args.enable_json: + print_error("Option --schema requires option --json") + return 99 +@@ -1579,8 +1573,8 @@ def main(): + tests = passed = warnings = errors = 0 + global log_file + try: +- log_file = tempfile.NamedTemporaryFile(prefix="nftables-test-py-", suffix=".log", mode='w', delete=auto_delete) +- if auto_delete: ++ log_file = tempfile.NamedTemporaryFile(prefix="nftables-test-py-", suffix=".log", mode='w', delete = not args.keep) ++ if not args.keep: + print_info("Log file %s will not be retained. Pass -k to keep it." % log_file.name) + else: + print_info("Log will be available at %s" % log_file.name) diff --git a/0299-datatype-don-t-return-a-const-string-from-cgroupv2_g.patch b/0299-datatype-don-t-return-a-const-string-from-cgroupv2_g.patch new file mode 100644 index 0000000..30bc4ec --- /dev/null +++ b/0299-datatype-don-t-return-a-const-string-from-cgroupv2_g.patch @@ -0,0 +1,52 @@ +From 2dde3c831187f0afdded2d1f94e1582be05b9d2b Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:20:22 +0200 +Subject: [PATCH] datatype: don't return a const string from + cgroupv2_get_path() + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 1f867d0d07122f54f76e20af3c636ce66102b683 + +commit 1f867d0d07122f54f76e20af3c636ce66102b683 +Author: Thomas Haller +Date: Tue Oct 24 11:57:07 2023 +0200 + + datatype: don't return a const string from cgroupv2_get_path() + + The caller is supposed to free the allocated string. Return a non-const + string to make that clearer. + + Signed-off-by: Thomas Haller + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/datatype.c | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +diff --git a/src/datatype.c b/src/datatype.c +index 1e7e12d..8404ff1 100644 +--- a/src/datatype.c ++++ b/src/datatype.c +@@ -1582,10 +1582,10 @@ const struct datatype policy_type = { + + #define SYSFS_CGROUPSV2_PATH "/sys/fs/cgroup" + +-static const char *cgroupv2_get_path(const char *path, uint64_t id) ++static char *cgroupv2_get_path(const char *path, uint64_t id) + { +- const char *cgroup_path = NULL; + char dent_name[PATH_MAX + 1]; ++ char *cgroup_path = NULL; + struct dirent *dent; + struct stat st; + DIR *d; +@@ -1623,7 +1623,7 @@ static void cgroupv2_type_print(const struct expr *expr, + struct output_ctx *octx) + { + uint64_t id = mpz_get_uint64(expr->value); +- const char *cgroup_path; ++ char *cgroup_path; + + cgroup_path = cgroupv2_get_path(SYSFS_CGROUPSV2_PATH, id); + if (cgroup_path) diff --git a/0300-rule-constify-set_is_non_concat_range.patch b/0300-rule-constify-set_is_non_concat_range.patch new file mode 100644 index 0000000..439cd1d --- /dev/null +++ b/0300-rule-constify-set_is_non_concat_range.patch @@ -0,0 +1,36 @@ +From fc83250b8c6186d0d620b1bbeef073255c650e93 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:20:22 +0200 +Subject: [PATCH] rule: constify set_is_non_concat_range() + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 0ff245fef0cff92651e464dd2d282f8d18fbfbc3 + +commit 0ff245fef0cff92651e464dd2d282f8d18fbfbc3 +Author: Pablo Neira Ayuso +Date: Fri Jan 3 17:40:56 2025 +0100 + + rule: constify set_is_non_concat_range() + + This is read-only, constify it. + + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + include/rule.h | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/include/rule.h b/include/rule.h +index 7b6551a..1a851ff 100644 +--- a/include/rule.h ++++ b/include/rule.h +@@ -420,7 +420,7 @@ static inline bool set_is_interval(uint32_t set_flags) + return set_flags & NFT_SET_INTERVAL; + } + +-static inline bool set_is_non_concat_range(struct set *s) ++static inline bool set_is_non_concat_range(const struct set *s) + { + return (s->flags & NFT_SET_INTERVAL) && s->desc.field_count <= 1; + } diff --git a/0301-tests-py-Fix-some-JSON-equivalents.patch b/0301-tests-py-Fix-some-JSON-equivalents.patch new file mode 100644 index 0000000..0c840b9 --- /dev/null +++ b/0301-tests-py-Fix-some-JSON-equivalents.patch @@ -0,0 +1,273 @@ +From bae32e53f806f0f6dc8fb27a5ce182ac8871f2f5 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 11:20:22 +0200 +Subject: [PATCH] tests: py: Fix some JSON equivalents + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 0c17b910b3e19c4eacf5567f90b6545ed1fd8aac + +commit 0c17b910b3e19c4eacf5567f90b6545ed1fd8aac +Author: Phil Sutter +Date: Fri Mar 22 14:00:26 2024 +0100 + + tests: py: Fix some JSON equivalents + + Make sure they match the standard syntax input as much as possible. + + For some reason inet/tcp.t.json was using plain arrays in place of + binary OR expressions in many cases. These arrays are interpreted as + list expressions, which seems to be semantically identical but the goal + here is to present an accurate equivalent to the rule in standard + syntax. + + Signed-off-by: Phil Sutter + +Signed-off-by: Phil Sutter +--- + tests/py/any/meta.t.json | 2 +- + tests/py/any/tcpopt.t.json | 4 +- + tests/py/inet/tcp.t.json | 124 +++++++++++++++++++++---------------- + 3 files changed, 75 insertions(+), 55 deletions(-) + +diff --git a/tests/py/any/meta.t.json b/tests/py/any/meta.t.json +index b315015..1c2f2b2 100644 +--- a/tests/py/any/meta.t.json ++++ b/tests/py/any/meta.t.json +@@ -2623,7 +2623,7 @@ + } + }, + "op": "==", +- "right": "17:00" ++ "right": "17:00:00" + } + }, + { +diff --git a/tests/py/any/tcpopt.t.json b/tests/py/any/tcpopt.t.json +index 859d612..65fa1de 100644 +--- a/tests/py/any/tcpopt.t.json ++++ b/tests/py/any/tcpopt.t.json +@@ -192,7 +192,7 @@ + "left": { + "tcp option": { + "field": "left", +- "name": "sack" ++ "name": "sack0" + } + }, + "op": "==", +@@ -272,7 +272,7 @@ + "left": { + "tcp option": { + "field": "right", +- "name": "sack" ++ "name": "sack0" + } + }, + "op": "==", +diff --git a/tests/py/inet/tcp.t.json b/tests/py/inet/tcp.t.json +index 95a9e48..88c6c59 100644 +--- a/tests/py/inet/tcp.t.json ++++ b/tests/py/inet/tcp.t.json +@@ -1339,13 +1339,13 @@ + "op": "==", + "right": { + "set": [ ++ "syn", + { + "|": [ + "syn", + "ack" + ] +- }, +- "syn" ++ } + ] + } + } +@@ -1370,10 +1370,10 @@ + "op": "==", + "right": { + "set": [ +- { "|": [ "fin", "psh", "ack" ] }, + "fin", ++ "ack", + { "|": [ "psh", "ack" ] }, +- "ack" ++ { "|": [ "fin", "psh", "ack" ] } + ] + } + } +@@ -1411,17 +1411,21 @@ + "protocol": "tcp" + } + }, +- [ +- "fin", +- "syn" +- ] ++ { ++ "|": [ ++ "fin", ++ "syn" ++ ] ++ } + ] + }, + "op": "==", +- "right": [ +- "fin", +- "syn" +- ] ++ "right": { ++ "|": [ ++ "fin", ++ "syn" ++ ] ++ } + } + } + ] +@@ -1438,10 +1442,12 @@ + "protocol": "tcp" + } + }, +- [ +- "fin", +- "syn" +- ] ++ { ++ "|": [ ++ "fin", ++ "syn" ++ ] ++ } + ] + }, + "op": "!=", +@@ -1574,12 +1580,14 @@ + "protocol": "tcp" + } + }, +- [ +- "fin", +- "syn", +- "rst", +- "ack" +- ] ++ { ++ "|": [ ++ "fin", ++ "syn", ++ "rst", ++ "ack" ++ ] ++ } + ] + }, + "op": "==", +@@ -1600,12 +1608,14 @@ + "protocol": "tcp" + } + }, +- [ +- "fin", +- "syn", +- "rst", +- "ack" +- ] ++ { ++ "|": [ ++ "fin", ++ "syn", ++ "rst", ++ "ack" ++ ] ++ } + ] + }, + "op": "==", +@@ -1627,12 +1637,14 @@ + "protocol": "tcp" + } + }, +- [ +- "fin", +- "syn", +- "rst", +- "ack" +- ] ++ { ++ "|": [ ++ "fin", ++ "syn", ++ "rst", ++ "ack" ++ ] ++ } + ] + }, + "op": "!=", +@@ -1653,19 +1665,23 @@ + "protocol": "tcp" + } + }, +- [ +- "fin", +- "syn", +- "rst", +- "ack" +- ] ++ { ++ "|": [ ++ "fin", ++ "syn", ++ "rst", ++ "ack" ++ ] ++ } + ] + }, + "op": "==", +- "right": [ +- "syn", +- "ack" +- ] ++ "right": { ++ "|": [ ++ "syn", ++ "ack" ++ ] ++ } + } + } + ] +@@ -1682,17 +1698,21 @@ + "protocol": "tcp" + } + }, +- [ +- "syn", +- "ack" +- ] ++ { ++ "|": [ ++ "syn", ++ "ack" ++ ] ++ } + ] + }, + "op": "==", +- "right": [ +- "syn", +- "ack" +- ] ++ "right": { ++ "|": [ ++ "syn", ++ "ack" ++ ] ++ } + } + } + ] diff --git a/0302-json-Dump-flowtable-hook-spec-only-if-present.patch b/0302-json-Dump-flowtable-hook-spec-only-if-present.patch new file mode 100644 index 0000000..9e1abf2 --- /dev/null +++ b/0302-json-Dump-flowtable-hook-spec-only-if-present.patch @@ -0,0 +1,78 @@ +From bac7112d952ab5d6e99cdfd650ca23a3126c2891 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 15:35:17 +0200 +Subject: [PATCH] json: Dump flowtable hook spec only if present + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 9fa3fbed73a36111e308c9856514a032f5444564 + +commit 9fa3fbed73a36111e308c9856514a032f5444564 +Author: Phil Sutter +Date: Wed Jun 11 16:45:48 2025 +0200 + + json: Dump flowtable hook spec only if present + + If there is no priority.expr set, assume hook.num is bogus, too. + + While this is fixing JSON output, it's hard to tell what commit this is + actually fixing: Before commit 627c451b23513 ("src: allow variables in + the chain priority specification"), there was no way to detect + flowtables missing hook specs (e.g. when printing flowtable delete + monitor event). + + Signed-off-by: Phil Sutter + Reviewed-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + src/json.c | 22 ++++++++++++++-------- + tests/monitor/testcases/flowtable-simple.t | 2 +- + 2 files changed, 15 insertions(+), 9 deletions(-) + +diff --git a/src/json.c b/src/json.c +index e26adce..79c3925 100644 +--- a/src/json.c ++++ b/src/json.c +@@ -493,18 +493,24 @@ static json_t *flowtable_print_json(const struct flowtable *ftable) + json_t *root, *devs = NULL; + int i, priority = 0; + ++ root = nft_json_pack("{s:s, s:s, s:s, s:I}", ++ "family", family2str(ftable->handle.family), ++ "name", ftable->handle.flowtable.name, ++ "table", ftable->handle.table.name, ++ "handle", ftable->handle.handle.id); ++ + if (ftable->priority.expr) { ++ json_t *tmp; ++ + mpz_export_data(&priority, ftable->priority.expr->value, + BYTEORDER_HOST_ENDIAN, sizeof(int)); +- } + +- root = nft_json_pack("{s:s, s:s, s:s, s:I, s:s, s:i}", +- "family", family2str(ftable->handle.family), +- "name", ftable->handle.flowtable.name, +- "table", ftable->handle.table.name, +- "handle", ftable->handle.handle.id, +- "hook", hooknum2str(NFPROTO_NETDEV, ftable->hook.num), +- "prio", priority); ++ tmp = nft_json_pack("{s:s, s:i}", ++ "hook", hooknum2str(NFPROTO_NETDEV, ++ ftable->hook.num), ++ "prio", priority); ++ json_object_update_new(root, tmp); ++ } + + for (i = 0; i < ftable->dev_array_len; i++) { + const char *dev = ftable->dev_array[i]; +diff --git a/tests/monitor/testcases/flowtable-simple.t b/tests/monitor/testcases/flowtable-simple.t +index df8eccb..b373cca 100644 +--- a/tests/monitor/testcases/flowtable-simple.t ++++ b/tests/monitor/testcases/flowtable-simple.t +@@ -7,4 +7,4 @@ J {"add": {"flowtable": {"family": "ip", "name": "ft", "table": "t", "handle": 0 + + I delete flowtable ip t ft + O - +-J {"delete": {"flowtable": {"family": "ip", "name": "ft", "table": "t", "handle": 0, "hook": "ingress", "prio": 0, "dev": "lo"}}} ++J {"delete": {"flowtable": {"family": "ip", "name": "ft", "table": "t", "handle": 0}}} diff --git a/0303-tests-monitor-enclose-device-names-in-quotes.patch b/0303-tests-monitor-enclose-device-names-in-quotes.patch new file mode 100644 index 0000000..f0cf4dc --- /dev/null +++ b/0303-tests-monitor-enclose-device-names-in-quotes.patch @@ -0,0 +1,41 @@ +From 9444607ab3dcf5fb8091bf894a65c1f161de580d Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 15:35:17 +0200 +Subject: [PATCH] tests: monitor: enclose device names in quotes + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 26746952952bba8c19aebbd03a55decbc0d0c5fc + +commit 26746952952bba8c19aebbd03a55decbc0d0c5fc +Author: Pablo Neira Ayuso +Date: Thu Jul 10 02:53:50 2025 +0200 + + tests: monitor: enclose device names in quotes + + Update test to enclose flowtable device names in quotes, otherwise, + it reports a spurious issue: + + @@ -1,2 +1,3 @@ + add table ip t + -add flowtable ip t ft { hook ingress priority 0; devices = { lo }; } + +add flowtable ip t ft { hook ingress priority 0; devices = { "lo" }; } + + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + tests/monitor/testcases/flowtable-simple.t | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/tests/monitor/testcases/flowtable-simple.t b/tests/monitor/testcases/flowtable-simple.t +index b373cca..11254c5 100644 +--- a/tests/monitor/testcases/flowtable-simple.t ++++ b/tests/monitor/testcases/flowtable-simple.t +@@ -1,6 +1,6 @@ + # setup first + I add table ip t +-I add flowtable ip t ft { hook ingress priority 0; devices = { lo }; } ++I add flowtable ip t ft { hook ingress priority 0; devices = { "lo" }; } + O - + J {"add": {"table": {"family": "ip", "name": "t", "handle": 0}}} + J {"add": {"flowtable": {"family": "ip", "name": "ft", "table": "t", "handle": 0, "hook": "ingress", "prio": 0, "dev": "lo"}}} diff --git a/0304-tests-monitor-Extend-testcases-a-bit.patch b/0304-tests-monitor-Extend-testcases-a-bit.patch new file mode 100644 index 0000000..13f24f4 --- /dev/null +++ b/0304-tests-monitor-Extend-testcases-a-bit.patch @@ -0,0 +1,120 @@ +From b9b90c2d9ad781549ce1cc23880ca86a7c8033f7 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Fri, 17 Jul 2026 15:35:17 +0200 +Subject: [PATCH] tests: monitor: Extend testcases a bit + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit d4b07cdae6de28744492fa104c57eeb9a8eaac9b + +commit d4b07cdae6de28744492fa104c57eeb9a8eaac9b +Author: Phil Sutter +Date: Fri Aug 29 01:49:01 2025 +0200 + + tests: monitor: Extend testcases a bit + + Try to cover for reduced table and chain deletion notifications by + creating them with data which is omitted by the kernel during deletion. + + Also try to expose the difference in reported flowtable hook deletion + vs. flowtable deletion. + + Signed-off-by: Phil Sutter + Acked-by: Pablo Neira Ayuso + +Signed-off-by: Phil Sutter +--- + tests/monitor/testcases/chain.t | 41 ++++++++++++++++++++++ + tests/monitor/testcases/flowtable-simple.t | 12 +++++++ + tests/monitor/testcases/table.t | 15 ++++++++ + 3 files changed, 68 insertions(+) + create mode 100644 tests/monitor/testcases/chain.t + create mode 100644 tests/monitor/testcases/table.t + +diff --git a/tests/monitor/testcases/chain.t b/tests/monitor/testcases/chain.t +new file mode 100644 +index 0000000..975ccf1 +--- /dev/null ++++ b/tests/monitor/testcases/chain.t +@@ -0,0 +1,41 @@ ++I add table inet t ++O - ++J {"add": {"table": {"family": "inet", "name": "t", "handle": 0}}} ++ ++I add chain inet t c ++O - ++J {"add": {"chain": {"family": "inet", "table": "t", "name": "c", "handle": 0}}} ++ ++I delete chain inet t c ++O - ++J {"delete": {"chain": {"family": "inet", "table": "t", "name": "c", "handle": 0}}} ++ ++I add chain inet t c { type filter hook input priority filter; } ++O add chain inet t c { type filter hook input priority 0; policy accept; } ++J {"add": {"chain": {"family": "inet", "table": "t", "name": "c", "handle": 0, "type": "filter", "hook": "input", "prio": 0, "policy": "accept"}}} ++ ++I delete chain inet t c ++O - ++J {"delete": {"chain": {"family": "inet", "table": "t", "name": "c", "handle": 0}}} ++ ++I add chain inet t c { type filter hook ingress priority filter; devices = { "lo" }; } ++O add chain inet t c { type filter hook ingress devices = { "lo" } priority 0; policy accept; } ++J {"add": {"chain": {"family": "inet", "table": "t", "name": "c", "handle": 0, "dev": "lo", "type": "filter", "hook": "ingress", "prio": 0, "policy": "accept"}}} ++ ++I delete chain inet t c ++O - ++J {"delete": {"chain": {"family": "inet", "table": "t", "name": "c", "handle": 0}}} ++ ++I add chain inet t c { type filter hook ingress priority filter; devices = { "eth1", "lo" }; } ++O add chain inet t c { type filter hook ingress devices = { "eth1", "lo" } priority 0; policy accept; } ++J {"add": {"chain": {"family": "inet", "table": "t", "name": "c", "handle": 0, "dev": ["eth1", "lo"], "type": "filter", "hook": "ingress", "prio": 0, "policy": "accept"}}} ++ ++I delete chain inet t c { type filter hook ingress priority filter; devices = { "eth1" }; } ++O delete chain inet t c { type filter hook ingress devices = { "eth1" } priority 0; policy accept; } ++J {"delete": {"chain": {"family": "inet", "table": "t", "name": "c", "handle": 0, "dev": "eth1", "type": "filter", "hook": "ingress", "prio": 0, "policy": "accept"}}} ++ ++I delete chain inet t c ++O - ++J {"delete": {"chain": {"family": "inet", "table": "t", "name": "c", "handle": 0}}} ++ ++ +diff --git a/tests/monitor/testcases/flowtable-simple.t b/tests/monitor/testcases/flowtable-simple.t +index 11254c5..e1889ae 100644 +--- a/tests/monitor/testcases/flowtable-simple.t ++++ b/tests/monitor/testcases/flowtable-simple.t +@@ -8,3 +8,15 @@ J {"add": {"flowtable": {"family": "ip", "name": "ft", "table": "t", "handle": 0 + I delete flowtable ip t ft + O - + J {"delete": {"flowtable": {"family": "ip", "name": "ft", "table": "t", "handle": 0}}} ++ ++I add flowtable ip t ft { hook ingress priority 0; devices = { "eth1", "lo" }; } ++O - ++J {"add": {"flowtable": {"family": "ip", "name": "ft", "table": "t", "handle": 0, "hook": "ingress", "prio": 0, "dev": ["eth1", "lo"]}}} ++ ++I delete flowtable ip t ft { hook ingress priority 0; devices = { "eth1" }; } ++O - ++J {"delete": {"flowtable": {"family": "ip", "name": "ft", "table": "t", "handle": 0, "hook": "ingress", "prio": 0, "dev": "eth1"}}} ++ ++I delete flowtable ip t ft ++O - ++J {"delete": {"flowtable": {"family": "ip", "name": "ft", "table": "t", "handle": 0}}} +diff --git a/tests/monitor/testcases/table.t b/tests/monitor/testcases/table.t +new file mode 100644 +index 0000000..35a0f51 +--- /dev/null ++++ b/tests/monitor/testcases/table.t +@@ -0,0 +1,15 @@ ++I add table ip t ++O - ++J {"add": {"table": {"family": "ip", "name": "t", "handle": 0}}} ++ ++I delete table ip t ++O - ++J {"delete": {"table": {"family": "ip", "name": "t", "handle": 0}}} ++ ++I add table ip t { comment "foo bar"; flags dormant; } ++O add table ip t { flags dormant; } ++J {"add": {"table": {"family": "ip", "name": "t", "handle": 0, "flags": ["dormant"], "comment": "foo bar"}}} ++ ++I delete table ip t ++O - ++J {"delete": {"table": {"family": "ip", "name": "t", "handle": 0}}} diff --git a/0305-tests-monitor-Label-diffs-to-help-users.patch b/0305-tests-monitor-Label-diffs-to-help-users.patch new file mode 100644 index 0000000..f584695 --- /dev/null +++ b/0305-tests-monitor-Label-diffs-to-help-users.patch @@ -0,0 +1,36 @@ +From d96974d910b7373fa92cdfb87f4277fdbe749954 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Tue, 21 Jul 2026 17:18:39 +0200 +Subject: [PATCH] tests: monitor: Label diffs to help users + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 53d686888bc446cc949c2013504c9974e93c1e2d + +commit 53d686888bc446cc949c2013504c9974e93c1e2d +Author: Phil Sutter +Date: Wed Sep 3 15:01:09 2025 +0200 + + tests: monitor: Label diffs to help users + + Clarify what was expected and what was actually received. + + Signed-off-by: Phil Sutter + +Signed-off-by: Phil Sutter +--- + tests/monitor/run-tests.sh | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/tests/monitor/run-tests.sh b/tests/monitor/run-tests.sh +index 7cbc7b1..1cceb0c 100755 +--- a/tests/monitor/run-tests.sh ++++ b/tests/monitor/run-tests.sh +@@ -6,7 +6,7 @@ debug=false + test_json=false + + mydiff() { +- diff -w -I '^# ' "$@" ++ diff -w -I '^# ' --label "expected" --label "got" "$@" + } + + err() { diff --git a/0306-tests-monitor-Run-in-own-netns.patch b/0306-tests-monitor-Run-in-own-netns.patch new file mode 100644 index 0000000..2321d28 --- /dev/null +++ b/0306-tests-monitor-Run-in-own-netns.patch @@ -0,0 +1,53 @@ +From 6fcecea15042c33d2150d7944f08866bfbbefbc0 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Tue, 21 Jul 2026 17:18:39 +0200 +Subject: [PATCH] tests: monitor: Run in own netns + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 4cbb233400e17664d817e4b638c2d8bb88b372ed + +commit 4cbb233400e17664d817e4b638c2d8bb88b372ed +Author: Phil Sutter +Date: Wed Oct 2 18:17:07 2024 +0200 + + tests: monitor: Run in own netns + + Have the script call itself prefixed by unshare. This won't prevent + clashing test case contents, but at least leave the host netns alone. + + Signed-off-by: Phil Sutter + +Signed-off-by: Phil Sutter +--- + tests/monitor/run-tests.sh | 11 +++++++++++ + 1 file changed, 11 insertions(+) + +diff --git a/tests/monitor/run-tests.sh b/tests/monitor/run-tests.sh +index 1cceb0c..7a00513 100755 +--- a/tests/monitor/run-tests.sh ++++ b/tests/monitor/run-tests.sh +@@ -117,6 +117,14 @@ echo_run_test() { + return $rc + } + ++netns=true ++for arg in "$@"; do ++ [[ "$arg" == "--no-netns" ]] && netns=false ++done ++if $netns; then ++ exec unshare -n $0 --no-netns "$@" ++fi ++ + testcases="" + while [ -n "$1" ]; do + case "$1" in +@@ -128,6 +136,9 @@ while [ -n "$1" ]; do + test_json=true + shift + ;; ++ --no-netns) ++ shift ++ ;; + -H|--host) + nft=nft + shift diff --git a/0307-tests-monitor-Become-PWD-agnostic.patch b/0307-tests-monitor-Become-PWD-agnostic.patch new file mode 100644 index 0000000..e737142 --- /dev/null +++ b/0307-tests-monitor-Become-PWD-agnostic.patch @@ -0,0 +1,49 @@ +From a508a65b32f289343e923e1ae65913de4ebee53e Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Tue, 21 Jul 2026 17:18:39 +0200 +Subject: [PATCH] tests: monitor: Become $PWD agnostic + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 83eaf50c36fe8710ae6b6f991f5f7c7e2ec8d513 + +commit 83eaf50c36fe8710ae6b6f991f5f7c7e2ec8d513 +Author: Phil Sutter +Date: Thu Nov 7 14:39:51 2024 +0100 + + tests: monitor: Become $PWD agnostic + + The call to 'cd' is problematic since later the script tries to 'exec + unshare -n $0'. This is not the only problem though: Individual test + cases specified on command line are expected to be relative to the + script's directory, too. Just get rid of these nonsensical restrictions. + + Reported-by: Florian Westphal + Signed-off-by: Phil Sutter + +Signed-off-by: Phil Sutter +--- + tests/monitor/run-tests.sh | 5 ++--- + 1 file changed, 2 insertions(+), 3 deletions(-) + +diff --git a/tests/monitor/run-tests.sh b/tests/monitor/run-tests.sh +index 7a00513..3ff8874 100755 +--- a/tests/monitor/run-tests.sh ++++ b/tests/monitor/run-tests.sh +@@ -1,7 +1,6 @@ + #!/bin/bash + +-cd $(dirname $0) +-nft=${NFT:-../../src/nft} ++nft=${NFT:-$(dirname $0)/../../src/nft} + debug=false + test_json=false + +@@ -143,7 +142,7 @@ while [ -n "$1" ]; do + nft=nft + shift + ;; +- testcases/*.t) ++ *.t) + testcases+=" $1" + shift + ;; diff --git a/0308-tests-monitor-Test-JSON-echo-mode-as-well.patch b/0308-tests-monitor-Test-JSON-echo-mode-as-well.patch new file mode 100644 index 0000000..4d6bb95 --- /dev/null +++ b/0308-tests-monitor-Test-JSON-echo-mode-as-well.patch @@ -0,0 +1,88 @@ +From c048668173d9ed624dc95124b46be6a5478aa332 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Tue, 21 Jul 2026 17:18:39 +0200 +Subject: [PATCH] tests: monitor: Test JSON echo mode as well + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 4a179d991b025a2a55762a6fe1e6c1a58fb5a677 + +commit 4a179d991b025a2a55762a6fe1e6c1a58fb5a677 +Author: Phil Sutter +Date: Wed Sep 3 15:30:31 2025 +0200 + + tests: monitor: Test JSON echo mode as well + + Reuse the expected JSON monitor output for --echo testing as it is + supposed to be "identical" - apart from formatting differences. To match + lines of commands (monitor output) against a single line of JSON object + (echo output), join the former's lines and drop the surrounding object + in the latter since this seems to be the simplest way. + + Signed-off-by: Phil Sutter + +Signed-off-by: Phil Sutter +--- + tests/monitor/run-tests.sh | 27 +++++++++++++++++++-------- + 1 file changed, 19 insertions(+), 8 deletions(-) + +diff --git a/tests/monitor/run-tests.sh b/tests/monitor/run-tests.sh +index 3ff8874..509b93f 100755 +--- a/tests/monitor/run-tests.sh ++++ b/tests/monitor/run-tests.sh +@@ -49,7 +49,7 @@ echo_output_append() { + grep '^\(add\|replace\|insert\)' $command_file >>$output_file + return + } +- [[ "$*" =~ ^(add|replace|insert) ]] && echo "$*" >>$output_file ++ [[ "$*" =~ ^(\{\")?(add|replace|insert) ]] && echo "$*" >>$output_file + } + json_output_filter() { # (filename) + # unify handle values +@@ -93,16 +93,32 @@ monitor_run_test() { + + echo_run_test() { + echo_output=$(mktemp -p $testdir) ++ echo_args="-nn -e" ++ $test_json && echo_args+=" -j" + local rc=0 + + $debug && { + echo "command file:" + cat $command_file + } +- $nft -nn -e -f - <$command_file >$echo_output || { ++ $nft $echo_args -f - <$command_file >$echo_output || { + err "nft command failed!" + rc=1 + } ++ if $test_json; then ++ # Extract commands from the surrounding JSON object ++ sed -i -e 's/^{"nftables": \[//' -e 's/\]}$//' $echo_output ++ json_output_filter $echo_output ++ ++ # Replace newlines by ", " in output file ++ readarray -t output_file_lines <$output_file ++ sep="" ++ for ((i = 0; i < ${#output_file_lines[*]}; i++)); do ++ printf "${sep}${output_file_lines[$i]}" ++ sep=", " ++ done >$output_file ++ [ $i -gt 0 ] && echo "" >>$output_file ++ fi + mydiff -q $echo_output $output_file >/dev/null 2>&1 + if [[ $rc == 0 && $? != 0 ]]; then + err "echo output differs!" +@@ -156,12 +172,7 @@ while [ -n "$1" ]; do + esac + done + +-if $test_json; then +- variants="monitor" +-else +- variants="monitor echo" +-fi +- ++variants="monitor echo" + rc=0 + for variant in $variants; do + run_test=${variant}_run_test diff --git a/0309-tests-monitor-Excercise-all-syntaxes-and-variants-by.patch b/0309-tests-monitor-Excercise-all-syntaxes-and-variants-by.patch new file mode 100644 index 0000000..7d3908e --- /dev/null +++ b/0309-tests-monitor-Excercise-all-syntaxes-and-variants-by.patch @@ -0,0 +1,192 @@ +From 45d0bb9b5ab300ebd3fa5285016e41f1bddcfcd5 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Tue, 21 Jul 2026 17:18:39 +0200 +Subject: [PATCH] tests: monitor: Excercise all syntaxes and variants by + default + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 48550e8e9359c4a2683e4e53cc413c661800c12a + +commit 48550e8e9359c4a2683e4e53cc413c661800c12a +Author: Phil Sutter +Date: Wed Sep 3 17:41:23 2025 +0200 + + tests: monitor: Excercise all syntaxes and variants by default + + Introduce -s/--standard flag to restrict execution to standard syntax + and let users select a specific variant by means of -e/--echo and + -m/--monitor flags. Run all four possible combinations by default. + + To keep indenting sane, introduce run_testcase() executing tests in a + single test case for a given syntax and variant. + + Signed-off-by: Phil Sutter + +Signed-off-by: Phil Sutter +--- + tests/monitor/run-tests.sh | 134 ++++++++++++++++++++++--------------- + 1 file changed, 79 insertions(+), 55 deletions(-) + +diff --git a/tests/monitor/run-tests.sh b/tests/monitor/run-tests.sh +index 509b93f..50e915e 100755 +--- a/tests/monitor/run-tests.sh ++++ b/tests/monitor/run-tests.sh +@@ -141,14 +141,28 @@ if $netns; then + fi + + testcases="" ++variants="" ++syntaxes="" + while [ -n "$1" ]; do + case "$1" in + -d|--debug) + debug=true + shift + ;; ++ -s|--standard) ++ syntaxes+=" standard" ++ shift ++ ;; + -j|--json) +- test_json=true ++ syntaxes+=" json" ++ shift ++ ;; ++ -e|--echo) ++ variants+=" echo" ++ shift ++ ;; ++ -m|--monitor) ++ variants+=" monitor" + shift + ;; + --no-netns) +@@ -166,64 +180,74 @@ while [ -n "$1" ]; do + echo "unknown option '$1'" + ;& + -h|--help) +- echo "Usage: $(basename $0) [-j|--json] [-d|--debug] [testcase ...]" ++ echo "Usage: $(basename $0) [(-e|--echo)|(-m|--monitor)] [(-j|--json)|(-s|--standard)] [-d|--debug] [testcase ...]" + exit 1 + ;; + esac + done + +-variants="monitor echo" +-rc=0 +-for variant in $variants; do +- run_test=${variant}_run_test +- output_append=${variant}_output_append +- +- for testcase in ${testcases:-testcases/*.t}; do +- filename=$(basename $testcase) +- echo "$variant: running tests from file $filename" +- rc_start=$rc +- +- # files are like this: +- # +- # I add table ip t +- # O add table ip t +- # I add chain ip t c +- # O add chain ip t c +- +- $nft flush ruleset +- +- input_complete=false +- while read dir line; do +- case $dir in +- I) +- $input_complete && { +- $run_test +- let "rc += $?" +- } +- input_complete=false +- cmd_append "$line" +- ;; +- O) +- input_complete=true +- $test_json || $output_append "$line" +- ;; +- J) +- input_complete=true +- $test_json && $output_append "$line" +- ;; +- '#'|'') +- # ignore comments and empty lines +- ;; +- esac +- done <$testcase +- $input_complete && { +- $run_test +- let "rc += $?" +- } +- +- let "rc_diff = rc - rc_start" +- [[ $rc_diff -ne 0 ]] && \ +- echo "$variant: $rc_diff tests from file $filename failed" ++# run the single test in $1 ++# expect $variant and $test_json to be set appropriately ++run_testcase() { ++ testcase="$1" ++ filename=$(basename $testcase) ++ rc=0 ++ $test_json && printf "json-" ++ echo "$variant: running tests from file $filename" ++ ++ # files are like this: ++ # ++ # I add table ip t ++ # O add table ip t ++ # I add chain ip t c ++ # O add chain ip t c ++ ++ $nft flush ruleset ++ ++ input_complete=false ++ while read dir line; do ++ case $dir in ++ I) ++ $input_complete && { ++ ${variant}_run_test ++ $run_test ++ let "rc += $?" ++ } ++ input_complete=false ++ cmd_append "$line" ++ ;; ++ O) ++ input_complete=true ++ $test_json || ${variant}_output_append "$line" ++ ;; ++ J) ++ input_complete=true ++ $test_json && ${variant}_output_append "$line" ++ ;; ++ '#'|'') ++ # ignore comments and empty lines ++ ;; ++ esac ++ done <$testcase ++ $input_complete && { ++ ${variant}_run_test ++ let "rc += $?" ++ } ++ ++ [[ $rc -ne 0 ]] && \ ++ echo "$variant: $rc tests from file $filename failed" ++ return $rc ++} ++ ++total_rc=0 ++for syntax in ${syntaxes:-standard json}; do ++ [ $syntax == json ] && test_json=true || test_json=false ++ for variant in ${variants:-echo monitor}; do ++ for testcase in ${testcases:-testcases/*.t}; do ++ run_testcase "$testcase" ++ let "total_rc += $?" ++ done + done + done +-exit $rc ++ ++exit $total_rc diff --git a/0310-tests-monitor-Fix-for-out-of-path-call.patch b/0310-tests-monitor-Fix-for-out-of-path-call.patch new file mode 100644 index 0000000..14a4041 --- /dev/null +++ b/0310-tests-monitor-Fix-for-out-of-path-call.patch @@ -0,0 +1,48 @@ +From 42e1fb111ee4600b8e165a9c62bc6cd834989248 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Tue, 21 Jul 2026 17:18:39 +0200 +Subject: [PATCH] tests: monitor: Fix for out-of-path call + +JIRA: https://issues.redhat.com/browse/RHEL-190549 +Upstream Status: nftables commit 1ac69c4de8ff7b472c3a5f2cf1d9442642e9047c + +commit 1ac69c4de8ff7b472c3a5f2cf1d9442642e9047c +Author: Phil Sutter +Date: Tue Dec 16 15:36:23 2025 +0100 + + tests: monitor: Fix for out-of-path call + + When called from another directory without specifying test cases, an + incorrect regexp was used to glob all tests and no test was run at all: + + | # ./tests/monitor/run-tests.sh + | echo: running tests from file *.t + | ./tests/monitor/run-tests.sh: line 201: testcases/*.t: No such file or directory + | monitor: running tests from file *.t + | ./tests/monitor/run-tests.sh: line 201: testcases/*.t: No such file or directory + | json-echo: running tests from file *.t + | ./tests/monitor/run-tests.sh: line 201: testcases/*.t: No such file or directory + | json-monitor: running tests from file *.t + | ./tests/monitor/run-tests.sh: line 201: testcases/*.t: No such file or directory + + Fixes: 83eaf50c36fe8 ("tests: monitor: Become $PWD agnostic") + Signed-off-by: Phil Sutter + +Signed-off-by: Phil Sutter +--- + tests/monitor/run-tests.sh | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/tests/monitor/run-tests.sh b/tests/monitor/run-tests.sh +index 50e915e..a81a7fe 100755 +--- a/tests/monitor/run-tests.sh ++++ b/tests/monitor/run-tests.sh +@@ -243,7 +243,7 @@ total_rc=0 + for syntax in ${syntaxes:-standard json}; do + [ $syntax == json ] && test_json=true || test_json=false + for variant in ${variants:-echo monitor}; do +- for testcase in ${testcases:-testcases/*.t}; do ++ for testcase in ${testcases:-$(dirname $0)/testcases/*.t}; do + run_testcase "$testcase" + let "total_rc += $?" + done diff --git a/monitor_run-tests.stderr.expect b/monitor_run-tests.stderr.expect new file mode 100644 index 0000000..3ddcb6f --- /dev/null +++ b/monitor_run-tests.stderr.expect @@ -0,0 +1,10 @@ +echo: 1 tests from file chain.t failed +echo: 1 tests from file flowtable-simple.t failed +monitor: 1 tests from file chain.t failed +monitor: 1 tests from file flowtable-simple.t failed +monitor: 1 tests from file table.t failed +echo: 1 tests from file chain.t failed +echo: 1 tests from file flowtable-simple.t failed +monitor: 1 tests from file chain.t failed +monitor: 1 tests from file flowtable-simple.t failed +monitor: 1 tests from file table.t failed diff --git a/nft-test.stderr.expect b/nft-test.stderr.expect index a883c88..d11d0fe 100644 --- a/nft-test.stderr.expect +++ b/nft-test.stderr.expect @@ -1,6 +1,6 @@ -any/tcpopt.t: ERROR: line 58: add rule ip test-ip4 input reset tcp option mptcp: This rule should not have failed. -any/tcpopt.t: ERROR: line 59: add rule ip test-ip4 input reset tcp option 2: This rule should not have failed. -any/tcpopt.t: ERROR: line 60: add rule ip test-ip4 input reset tcp option 123: This rule should not have failed. +any/tcpopt.t: ERROR: line 59: add rule ip test-ip4 input reset tcp option mptcp: This rule should not have failed. +any/tcpopt.t: ERROR: line 60: add rule ip test-ip4 input reset tcp option 2: This rule should not have failed. +any/tcpopt.t: ERROR: line 61: add rule ip test-ip4 input reset tcp option 123: This rule should not have failed. bridge/redirect.t: ERROR: line 5: add rule bridge test-bridge prerouting meta broute set 1: This rule should not have failed. inet/dccp.t: ERROR: line 27: add rule ip test-ip4 input dccp option 0 exists: This rule should not have failed. inet/dccp.t: ERROR: line 28: add rule ip test-ip4 input dccp option 43 missing: This rule should not have failed. diff --git a/nftables.spec b/nftables.spec index 962ea05..34a2a15 100644 --- a/nftables.spec +++ b/nftables.spec @@ -1,5 +1,5 @@ %define nft_rpmversion 1.0.9 -%define nft_specrelease 7 +%define nft_specrelease 8 Name: nftables Version: %{nft_rpmversion} @@ -18,6 +18,7 @@ Source4: router.nft Source5: nat.nft Source6: nft-test.stderr.expect Source7: run-tests.stderr.expect +Source8: monitor_run-tests.stderr.expect Patch1: 0001-Add-support-for-table-s-persist-flag.patch Patch2: 0002-cache-Always-set-NFT_CACHE_TERSE-for-list-cmd-with-t.patch @@ -34,6 +35,301 @@ Patch12: 0012-Revert-doc-part-of-meta-introduce-meta-broute-suppor.pa Patch13: 0013-Revert-doc-part-of-exthdr-add-boolean-DCCP-option-ma.patch Patch14: 0014-src-netlink-fix-crash-when-ops-doesn-t-support-udata.patch Patch15: 0015-src-fix-reset-element-support-for-interval-set-type.patch +Patch16: 0016-parser_bison-Fix-for-broken-compatibility-with-older.patch +Patch17: 0017-tests-shell-add-missing-elem_opts_compat_0.nodump-fi.patch +Patch18: 0018-tests-shell-cover-long-interface-name-in-0042chain_v.patch +Patch19: 0019-parser_bison-fix-length-check-for-ifname-in-ifname_e.patch +Patch20: 0020-tests-shell-fix-mount-command-in-test-wrapper.sh.patch +Patch21: 0021-tproxy-Drop-artificial-port-printing-restriction.patch +Patch22: 0022-meta-fix-hour-decoding-when-timezone-offset-is-negat.patch +Patch23: 0023-evaluate-reset-statement-length-context-only-for-set.patch +Patch24: 0024-evaluate-place-byteorder-conversion-before-rshift-in.patch +Patch25: 0025-src-add-free_const-and-use-it-instead-of-xfree.patch +Patch26: 0026-src-remove-xfree-and-use-plain-free.patch +Patch27: 0027-netlink-fix-buffer-size-for-user-data-in-netlink_del.patch +Patch28: 0028-tests-shell-split-merge-nat-optimization-in-two-test.patch +Patch29: 0029-tests-shell-split-set-NAT-interval-test.patch +Patch30: 0030-src-expand-create-commands.patch +Patch31: 0031-json-fix-use-after-free-in-table_flags_json.patch +Patch32: 0032-tests-shell-restore-pipapo-and-chain-binding-coverag.patch +Patch33: 0033-tests-shell-skip-if-kernel-does-not-support-flowtabl.patch +Patch34: 0034-evaluate-fix-rule-replacement-with-anon-sets.patch +Patch35: 0035-tests-shell-adjust-add-after-delete-flowtable-for-ol.patch +Patch36: 0036-tests-shell-flush-connlimit-sets.patch +Patch37: 0037-evaluate-bogus-error-when-adding-devices-to-flowtabl.patch +Patch38: 0038-tests-shell-split-nat-inet-tests.patch +Patch39: 0039-evaluate-clone-unary-expression-datatype-to-deal-wit.patch +Patch40: 0040-evaluate-reject-sets-with-no-key.patch +Patch41: 0041-evaluate-prevent-assert-when-evaluating-very-large-s.patch +Patch42: 0042-evaluate-disable-meta-set-with-ranges.patch +Patch43: 0043-monitor-add-support-for-concatenated-set-ranges.patch +Patch44: 0044-evaluate-reject-set-definition-with-no-key.patch +Patch45: 0045-parser-tcpopt-fix-tcp-option-parsing-with-NUM-length.patch +Patch46: 0046-evaluate-reset-statement-length-context-before-evalu.patch +Patch47: 0047-tests-py-missing-json-output-in-meta.t-with-vlan-map.patch +Patch48: 0048-parser_bison-fix-objref-statement-corruption.patch +Patch49: 0049-evaluate-fix-bogus-assertion-failure-with-boolean-da.patch +Patch50: 0050-parser_bison-close-chain-scope-before-chain-release.patch +Patch51: 0051-meta-fix-tc-classid-parsing-out-of-bounds-access.patch +Patch52: 0052-evaluate-exthdr-statement-arg-must-be-not-be-a-range.patch +Patch53: 0053-src-reject-large-raw-payload-and-concat-expressions.patch +Patch54: 0054-evaluate-fix-stack-overflow-with-huge-priority-strin.patch +Patch55: 0055-tests-shell-add-test-to-cover-payload-transport-matc.patch +Patch56: 0056-parser_bison-error-out-on-duplicated-type-typeof-ele.patch +Patch57: 0057-netlink-fix-stack-overflow-due-to-erroneous-rounding.patch +Patch58: 0058-parser_bison-ensure-all-timeout-policy-names-are-rel.patch +Patch59: 0059-tests-shell-prefer-project-nft-to-system-wide-nft.patch +Patch60: 0060-doc-incorrect-datatype-description-for-icmpv6_type-a.patch +Patch61: 0061-evaluate-add-missing-range-checks-for-dup-fwd-and-pa.patch +Patch62: 0062-evaluate-skip-anonymous-set-optimization-for-concate.patch +Patch63: 0063-evaluate-do-not-fetch-next-expression-on-runaway-num.patch +Patch64: 0064-evaluate-error-out-when-store-needs-more-than-one-12.patch +Patch65: 0065-rule-fix-sym-refcount-assertion.patch +Patch66: 0066-json-Support-sets-auto-merge-option.patch +Patch67: 0067-evaluate-don-t-assert-on-net-transport-header-confli.patch +Patch68: 0068-evaluate-fix-check-for-unknown-in-cmd_op_to_name.patch +Patch69: 0069-cache-Optimize-caching-for-list-tables-command.patch +Patch70: 0070-evaluate-skip-byteorder-conversion-for-selector-smal.patch +Patch71: 0071-netlink_delinearize-move-concat-and-value-postproces.patch +Patch72: 0072-expression-missing-line-in-describe-command-with-inv.patch +Patch73: 0073-evaluate-permit-use-of-host-endian-constant-values-i.patch +Patch74: 0074-tests-shell-permit-use-of-host-endian-constant-value.patch +Patch75: 0075-src-improve-error-reporting-for-destroy-command.patch +Patch76: 0076-parser-compact-interval-typeof-rules.patch +Patch77: 0077-parser-compact-type-typeof-set-rules.patch +Patch78: 0078-rule-fix-ASAN-errors-in-chain-priority-to-textual-na.patch +Patch79: 0079-nftables-do-mot-merge-payloads-on-negation.patch +Patch80: 0080-tests-py-add-payload-merging-test-cases.patch +Patch81: 0081-parser-json-Support-for-synproxy-objects.patch +Patch82: 0082-doc-libnftables-json-Drop-invalid-ops-from-match-exp.patch +Patch83: 0083-netlink_delinearize-restore-binop-syntax-when-listin.patch +Patch84: 0084-evaluate-display-Range-negative-size-error.patch +Patch85: 0085-src-disentangle-ICMP-code-types.patch +Patch86: 0086-tests-py-complete-icmp-and-icmpv6-update.patch +Patch87: 0087-tests-shell-payload-matching-requires-egress-support.patch +Patch88: 0088-tests-shell-check-for-reset-tcp-options-support.patch +Patch89: 0089-doc-nft.8-Two-minor-synopsis-fixups.patch +Patch90: 0090-mergesort-Avoid-accidental-set-element-reordering.patch +Patch91: 0091-doc-nft.8-Fix-markup-in-ct-expectation-synopsis.patch +Patch92: 0092-cache-check-for-NFT_CACHE_REFRESH-in-current-request.patch +Patch93: 0093-evaluate-bogus-protocol-conflicts-in-vlan-with-impli.patch +Patch94: 0094-evaluate-Fix-incorrect-checking-the-base-variable-in.patch +Patch95: 0095-scanner-inet_pton-allows-for-broader-IPv4-Mapped-IPv.patch +Patch96: 0096-monitor-too-large-shift-exponent-displaying-payload-.patch +Patch97: 0097-cmd-provide-better-hint-if-chain-is-already-declared.patch +Patch98: 0098-cmd-skip-variable-set-elements-when-collapsing-comma.patch +Patch99: 0099-tests-shell-skip-ip-option-tests-if-kernel-does-not-.patch +Patch100: 0100-src-add-string-preprocessor-and-use-it-for-log-prefi.patch +Patch101: 0101-intervals-fix-element-deletions-with-maps.patch +Patch102: 0102-parser_bison-recursive-table-declaration-in-deprecat.patch +Patch103: 0103-evaluate-set-on-expr-len-for-catchall-set-elements.patch +Patch104: 0104-segtree-set-on-EXPR_F_KERNEL-flag-for-catchall-eleme.patch +Patch105: 0105-optimize-clone-counter-before-insertion-into-set-ele.patch +Patch106: 0106-libnftables-skip-useable-checks-for-dev-stdin.patch +Patch107: 0107-parser_json-use-stdin-buffer-if-available.patch +Patch108: 0108-optimize-skip-variables-in-nat-statements.patch +Patch109: 0109-datatype-reject-rate-in-quota-statement.patch +Patch110: 0110-cache-rule-by-index-requires-full-cache.patch +Patch111: 0111-datatype-improve-error-reporting-when-time-unit-is-n.patch +Patch112: 0112-parser_bison-allow-0-burst-in-limit-rate-byte-mode.patch +Patch113: 0113-parser_json-fix-crash-in-json_parse_set_stmt_list.patch +Patch114: 0114-json-Support-maps-with-concatenated-data.patch +Patch115: 0115-parser_json-release-buffer-returned-by-json_dumps.patch +Patch116: 0116-parser_json-fix-several-expression-memleaks-from-err.patch +Patch117: 0117-cache-reset-filter-for-each-command.patch +Patch118: 0118-cache-accumulate-flags-in-batch.patch +Patch119: 0119-cache-only-dump-rules-for-the-given-table.patch +Patch120: 0120-cache-add-filtering-support-for-objects.patch +Patch121: 0121-cache-consolidate-reset-command.patch +Patch122: 0122-cache-assert-filter-when-calling-nft_cache_evaluate.patch +Patch123: 0123-cache-remove-full-cache-requirement-when-echo-flag-i.patch +Patch124: 0124-cache-relax-requirement-for-replace-rule-command.patch +Patch125: 0125-cache-position-does-not-require-full-cache.patch +Patch126: 0126-libnftables-Zero-ctx-vars-after-freeing-it.patch +Patch127: 0127-tests-shell-stabilize-packetpath-payload.patch +Patch128: 0128-proto-use-NFT_PAYLOAD_L4CSUM_PSEUDOHDR-flag-to-mangl.patch +Patch129: 0129-tests-py-fix-up-udp-csum-fixup-output.patch +Patch130: 0130-cache-initialize-filter-when-fetching-implicit-chain.patch +Patch131: 0131-libnftables-json-fix-raw-payload-expression-document.patch +Patch132: 0132-src-collapse-set-element-commands-from-parser.patch +Patch133: 0133-rule-netlink-attribute-offset-is-uint32_t-for-struct.patch +Patch134: 0134-mnl-update-cmd_add_loc-to-take-struct-nlmsghdr.patch +Patch135: 0135-mnl-rename-to-mnl_seqnum_alloc-to-mnl_seqnum_inc.patch +Patch136: 0136-src-fix-extended-netlink-error-reporting-with-large-.patch +Patch137: 0137-tests-monitor-fix-up-test-case-breakage.patch +Patch138: 0138-doc-extend-description-of-fib-expression.patch +Patch139: 0139-json-collapse-set-element-commands-from-parser.patch +Patch140: 0140-json-Support-typeof-in-set-and-map-types.patch +Patch141: 0141-tests-py-Fix-for-storing-payload-into-missing-file.patch +Patch142: 0142-optimize-compare-expression-length.patch +Patch143: 0143-intervals-set-internal-element-location-with-the-del.patch +Patch144: 0144-parser_bison-fix-UaF-when-reporting-table-parse-erro.patch +Patch145: 0145-intervals-add-helper-function-to-set-previous-elemen.patch +Patch146: 0146-src-add-EXPR_RANGE_VALUE-expression-and-use-it.patch +Patch147: 0147-intervals-do-not-merge-intervals-with-different-time.patch +Patch148: 0148-evaluate-remove-variable-shadowing.patch +Patch149: 0149-netlink_delinarize-fix-bogus-munging-of-mask-value.patch +Patch150: 0150-ipopt-use-ipv4-address-datatype-for-address-field-in.patch +Patch151: 0151-tests-shell-delete-netdev-chain-after-test.patch +Patch152: 0152-datatype-clamp-boolean-value-to-0-and-1.patch +Patch153: 0153-parser_bison-turn-redundant-ip-option-type-field-mat.patch +Patch154: 0154-parser_bison-compact-and-simplify-list-and-reset-syn.patch +Patch155: 0155-evaluate-auto-merge-is-only-available-for-singleton-.patch +Patch156: 0156-fib-Change-data-type-of-fib-oifname-to-ifname.patch +Patch157: 0157-evaluate-optimize-zero-length-range.patch +Patch158: 0158-payload-return-early-if-dependency-is-not-a-payload-.patch +Patch159: 0159-payload-honor-inner-payload-description-in-payload_e.patch +Patch160: 0160-segtree-fix-string-data-initialisation.patch +Patch161: 0161-evaluate-release-existing-datatype-when-evaluating-u.patch +Patch162: 0162-segtree-incomplete-output-in-get-element-command-wit.patch +Patch163: 0163-src-do-not-merge-a-set-with-a-erroneous-one.patch +Patch164: 0164-tests-shell-detach-synproxy-test.patch +Patch165: 0165-src-print-set-element-with-multi-word-description-in.patch +Patch166: 0166-netlink-fix-stack-buffer-overrun-when-emitting-range.patch +Patch167: 0167-parser_bison-reject-non-serializeable-typeof-express.patch +Patch168: 0168-evaluate-don-t-allow-nat-map-with-specified-protocol.patch +Patch169: 0169-evaluate-fix-assertion-failure-with-malformed-map-de.patch +Patch170: 0170-tests-shell-extend-coverage-for-set-element-statemen.patch +Patch171: 0171-parser_bison-consolidate-counter-grammar-rule-for-se.patch +Patch172: 0172-parser_bison-consolidate-limit-grammar-rule-for-set-.patch +Patch173: 0173-parser_bison-consolidate-quota-grammar-rule-for-set-.patch +Patch174: 0174-parser_bison-consolidate-last-grammar-rule-for-set-e.patch +Patch175: 0175-parser_bison-consolidate-connlimit-grammar-rule-for-.patch +Patch176: 0176-json-make-sure-timeout-list-is-initialised.patch +Patch177: 0177-evaluate-don-t-update-cache-for-anonymous-chains.patch +Patch178: 0178-tests-shell-missing-ct-count-elements-in-new-set_stm.patch +Patch179: 0179-optimize-compact-bitmask-matching-in-set-map.patch +Patch180: 0180-optimize-incorrect-comparison-for-reject-statement.patch +Patch181: 0181-json-don-t-BUG-when-asked-to-list-synproxies.patch +Patch182: 0182-evaluate-compact-STMT_F_STATEFUL-checks.patch +Patch183: 0183-evaluate-only-allow-stateful-statements-in-set-and-m.patch +Patch184: 0184-cache-don-t-crash-when-filter-is-NULL.patch +Patch185: 0185-optimize-expand-expression-list-when-merging-into-co.patch +Patch186: 0186-cache-prevent-possible-crash-rule-filter-is-NULL.patch +Patch187: 0187-parser_json-allow-statement-stateful-statement-only-.patch +Patch188: 0188-parser_json-reject-empty-jump-goto-chain.patch +Patch189: 0189-parser_json-bail-out-on-malformed-statement-in-set.patch +Patch190: 0190-evaluate-bail-out-if-ct-saddr-daddr-dependency-canno.patch +Patch191: 0191-optimize-invalidate-merge-in-case-of-duplicated-key-.patch +Patch192: 0192-parser_bison-add-selector_expr-rule-to-restrict-type.patch +Patch193: 0193-netlink-bogus-concatenated-set-ranges-with-netlink-m.patch +Patch194: 0194-doc-Fix-typo-in-nat-statement-prefix-description.patch +Patch195: 0195-parser_json-Introduce-parse_flags_array.patch +Patch196: 0196-json-Print-single-synproxy-flags-as-non-array.patch +Patch197: 0197-json-Print-single-set-flag-as-non-array.patch +Patch198: 0198-tests-shell-Add-test-case-for-JSON-flags-arrays.patch +Patch199: 0199-json-Print-single-fib-flag-as-non-array.patch +Patch200: 0200-json-Accept-more-than-two-operands-in-binary-express.patch +Patch201: 0201-json-Fix-for-memleak-in-__binop_expr_json.patch +Patch202: 0202-json-Introduce-json_add_array_new.patch +Patch203: 0203-netlink-Catch-unknown-types-when-deserializing-objec.patch +Patch204: 0204-netlink-Avoid-potential-NULL-ptr-deref-parsing-set-e.patch +Patch205: 0205-tests-shell-check-for-features-not-available-in-5.4.patch +Patch206: 0206-tests-py-fix-json-single-flag-output-for-fib-synprox.patch +Patch207: 0207-json-prevent-null-deref-if-chain-policy-is-not-set.patch +Patch208: 0208-json-work-around-fuzzer-induced-assert-crashes.patch +Patch209: 0209-netlink-Fix-for-potential-crash-parsing-a-flowtable.patch +Patch210: 0210-netlink-Do-not-allocate-a-bogus-flowtable-priority-e.patch +Patch211: 0211-tests-monitor-Fix-for-single-flag-array-avoidance.patch +Patch212: 0212-tests-shell-combine-dormant-flag-with-netdevice-remo.patch +Patch213: 0213-tests-shell-Adjust-to-ifname-based-hooks.patch +Patch214: 0214-tests-py-Properly-fix-JSON-equivalents-for-netdev-re.patch +Patch215: 0215-netlink-Avoid-crash-upon-missing-NFTNL_OBJ_CT_TIMEOU.patch +Patch216: 0216-src-BASECHAIN-flag-no-longer-implies-presence-of-pri.patch +Patch217: 0217-tests-py-prepare-for-set-debug-change.patch +Patch218: 0218-debug-include-kernel-set-information-on-cache-fill.patch +Patch219: 0219-parser_bison-allow-delete-command-with-map-via-handl.patch +Patch220: 0220-rule-skip-fuzzy-lookup-if-object-name-is-not-availab.patch +Patch221: 0221-parser_bison-only-reset-by-name-is-supported-by-now.patch +Patch222: 0222-cache-assert-name-is-non-nul-when-looking-up.patch +Patch223: 0223-tests-py-clean-up-set-backend-support-fallout.patch +Patch224: 0224-json-reject-too-long-interface-names.patch +Patch225: 0225-tests-shell-Fix-ifname_based_hooks-feature-check.patch +Patch226: 0226-fib-allow-to-check-if-route-exists-in-maps.patch +Patch227: 0227-fib-allow-to-use-it-in-set-statements.patch +Patch228: 0228-tests-py-re-enables-nft-test.py-to-load-the-local-nf.patch +Patch229: 0229-rule-print-chain-and-flowtable-devices-in-quotes.patch +Patch230: 0230-evaluate-validate-set-expression-type-before-accessi.patch +Patch231: 0231-mnl-Call-mnl_attr_nest_end-just-once.patch +Patch232: 0232-doc-nft.8-Minor-NAT-STATEMENTS-section-review.patch +Patch233: 0233-src-netlink-netlink_delinearize_table-may-return-NUL.patch +Patch234: 0234-tests-py-Drop-stale-entries-since-redundant-test-cas.patch +Patch235: 0235-tests-py-Drop-duplicate-test-from-inet-geneve.t.patch +Patch236: 0236-tests-py-Drop-duplicate-test-from-inet-gre.t.patch +Patch237: 0237-tests-py-Drop-duplicate-test-from-inet-gretap.t.patch +Patch238: 0238-tests-py-Drop-stale-entry-from-inet-tcp.t.json.patch +Patch239: 0239-tests-py-Drop-duplicate-test-from-inet-vxlan.t.patch +Patch240: 0240-tests-py-Drop-stale-entry-from-ip-snat.t.json.patch +Patch241: 0241-tests-py-Drop-stale-entries-from-ip6-ct-meta-.t.json.patch +Patch242: 0242-tests-py-Drop-stale-entry-from-ip-snat.t.payload.patch +Patch243: 0243-tests-py-Fix-tests-added-for-icmpv6-taddr-support.patch +Patch244: 0244-json-Do-not-reduce-single-item-arrays-on-output.patch +Patch245: 0245-segtree-incorrect-type-when-aggregating-concatenated.patch +Patch246: 0246-src-ensure-chain-policy-evaluation-when-specified.patch +Patch247: 0247-fib-restore-JSON-output-for-relational-expressions.patch +Patch248: 0248-mnl-silence-compiler-warning.patch +Patch249: 0249-tests-monitor-Fix-for-flag-arrays-in-JSON-output.patch +Patch250: 0250-mnl-continue-on-ENOBUFS-errors-when-processing-batch.patch +Patch251: 0251-monitor-Quote-device-names-in-chain-declarations-too.patch +Patch252: 0252-tests-monitor-Fix-regex-collecting-expected-echo-out.patch +Patch253: 0253-monitor-Recognize-flowtable-add-del-events.patch +Patch254: 0254-monitor-Inform-JSON-printer-when-reporting-an-object.patch +Patch255: 0255-tests-Prepare-exit-codes-for-automake.patch +Patch256: 0256-fib-Fix-for-existence-check-on-Big-Endian.patch +Patch257: 0257-tests-py-objects.t-must-use-input-not-output.patch +Patch258: 0258-doc-fix-tcpdump-example.patch +Patch259: 0259-src-parser_json-fix-format-string-bugs.patch +Patch260: 0260-optimize-Fix-verdict-expression-comparison.patch +Patch261: 0261-datatype-Fix-boolean-type-on-Big-Endian.patch +Patch262: 0262-tests-py-any-ct.t.json.output-Drop-leftover-entry.patch +Patch263: 0263-tests-py-Fix-for-using-wrong-payload-path.patch +Patch264: 0264-tests-py-Do-not-rely-upon-end-marker.patch +Patch265: 0265-tests-py-inet-osf.t-Fix-element-ordering-in-JSON-equ.patch +Patch266: 0266-tests-py-Implement-payload_record.patch +Patch267: 0267-doc-don-t-suggest-to-disable-GSO.patch +Patch268: 0268-doc-libnftables-json-Describe-RULESET-object.patch +Patch269: 0269-rule-skip-CMD_OBJ_SETELEMS-with-no-elements-after-se.patch +Patch270: 0270-tests-json_echo-Drop-rule-handle-before-multi-add.patch +Patch271: 0271-monitor-fix-memleak-in-setelem-cb.patch +Patch272: 0272-tcpopt-add-symbol-table-for-mptcp-suboptions.patch +Patch273: 0273-netlink_delinearize-also-consider-exthdr-type-when-t.patch +Patch274: 0274-expression-propagate-key-datatype-for-anonymous-sets.patch +Patch275: 0275-expression-expr_build_udata_recurse-should-recurse.patch +Patch276: 0276-tests-py-any-tcpopt.t.json-Fix-JSON-equivalent.patch +Patch277: 0277-mergesort-Fix-sorting-of-string-values.patch +Patch278: 0278-mergesort-Align-concatenation-sort-order-with-Big-En.patch +Patch279: 0279-segtree-Fix-range-aggregation-on-Big-Endian.patch +Patch280: 0280-json-complete-multi-statement-set-element-support.patch +Patch281: 0281-parser_bison-add-range-check-for-synproxy-wscale.patch +Patch282: 0282-cache-Relax-chain_cache_dump-filter-application.patch +Patch283: 0283-cache-Include-chains-flowtables-and-objects-in-netli.patch +Patch284: 0284-cache-Respect-family-in-all-list-commands.patch +Patch285: 0285-cache-Filter-for-table-when-listing-sets-or-maps.patch +Patch286: 0286-cache-Filter-for-table-when-listing-flowtables.patch +Patch287: 0287-segtree-Fix-for-variable-sized-object-may-not-be-ini.patch +Patch288: 0288-mnl-Fix-ordering-of-hooks-in-list-hooks-output.patch +Patch289: 0289-tests-py-don-t-use-a-fixed-filename.patch +Patch290: 0290-tests-py-print-the-file-name-as-intended.patch +Patch291: 0291-tests-py-osf-is-ip-only.patch +Patch292: 0292-cache-honor-c-check-for-reset-commands.patch +Patch293: 0293-parser_json-fix-map-set-type-confusion-crash-in-map-.patch +Patch294: 0294-segtree-rename-set_elem_add-to-set_elem_expr_add.patch +Patch295: 0295-src-remove-EXPR_SET_ELEM-in-range_expr_value_-low-hi.patch +Patch296: 0296-segtree-replace-default-case-by-specific-types-in-ge.patch +Patch297: 0297-segtree-fix-get-element-command-with-open-intervals.patch +Patch298: 0298-tests-py-Fix-keep-test-runner-option.patch +Patch299: 0299-datatype-don-t-return-a-const-string-from-cgroupv2_g.patch +Patch300: 0300-rule-constify-set_is_non_concat_range.patch +Patch301: 0301-tests-py-Fix-some-JSON-equivalents.patch +Patch302: 0302-json-Dump-flowtable-hook-spec-only-if-present.patch +Patch303: 0303-tests-monitor-enclose-device-names-in-quotes.patch +Patch304: 0304-tests-monitor-Extend-testcases-a-bit.patch +Patch305: 0305-tests-monitor-Label-diffs-to-help-users.patch +Patch306: 0306-tests-monitor-Run-in-own-netns.patch +Patch307: 0307-tests-monitor-Become-PWD-agnostic.patch +Patch308: 0308-tests-monitor-Test-JSON-echo-mode-as-well.patch +Patch309: 0309-tests-monitor-Excercise-all-syntaxes-and-variants-by.patch +Patch310: 0310-tests-monitor-Fix-for-out-of-path-call.patch BuildRequires: autoconf BuildRequires: automake @@ -79,6 +375,7 @@ The nftables python module provides an interface to libnftables via ctypes. %autosetup -p1 cp -a %{SOURCE6} ./tests/py/ cp -a %{SOURCE7} ./tests/shell/ +cp -a %{SOURCE8} ./tests/monitor/run-tests.stderr.expect %build autoreconf -fi @@ -146,6 +443,304 @@ cd py/ %files -n python3-nftables -f %{pyproject_files} %changelog +* Wed Jul 22 2026 Phil Sutter [1.0.9-8.el9] +- spec: Update expected test suite results (Phil Sutter) [RHEL-190549] +- tests: monitor: Fix for out-of-path call (Phil Sutter) [RHEL-190549] +- tests: monitor: Excercise all syntaxes and variants by default (Phil Sutter) [RHEL-190549] +- tests: monitor: Test JSON echo mode as well (Phil Sutter) [RHEL-190549] +- tests: monitor: Become $PWD agnostic (Phil Sutter) [RHEL-190549] +- tests: monitor: Run in own netns (Phil Sutter) [RHEL-190549] +- tests: monitor: Label diffs to help users (Phil Sutter) [RHEL-190549] +- tests: monitor: Extend testcases a bit (Phil Sutter) [RHEL-190549] +- tests: monitor: enclose device names in quotes (Phil Sutter) [RHEL-190549] +- json: Dump flowtable hook spec only if present (Phil Sutter) [RHEL-190549] +- tests: py: Fix some JSON equivalents (Phil Sutter) [RHEL-190549] +- rule: constify set_is_non_concat_range() (Phil Sutter) [RHEL-190549] +- datatype: don't return a const string from cgroupv2_get_path() (Phil Sutter) [RHEL-190549] +- tests: py: Fix --keep test runner option (Phil Sutter) [RHEL-190549] +- segtree: fix get element command with open intervals (Phil Sutter) [RHEL-190549] +- segtree: replace default case by specific types in get_set_intervals() (Phil Sutter) [RHEL-190549] +- src: remove EXPR_SET_ELEM in range_expr_value_{low,high}() (Phil Sutter) [RHEL-190549] +- segtree: rename set_elem_add() to set_elem_expr_add() (Phil Sutter) [RHEL-190549] +- parser_json: fix map/set type confusion crash in map statement parser (Phil Sutter) [RHEL-190549] +- cache: honor -c/--check for reset commands (Phil Sutter) [RHEL-190549] +- tests: py: osf is ip-only (Phil Sutter) [RHEL-190549] +- tests: py: print the file name as intended (Phil Sutter) [RHEL-190549] +- tests: py: don't use a fixed filename (Phil Sutter) [RHEL-190549] +- mnl: Fix ordering of hooks in 'list hooks' output (Phil Sutter) [RHEL-190549] +- segtree: Fix for variable-sized object may not be initialized (Phil Sutter) [RHEL-190549] +- cache: Filter for table when listing flowtables (Phil Sutter) [RHEL-190549] +- cache: Filter for table when listing sets or maps (Phil Sutter) [RHEL-190549] +- cache: Respect family in all list commands (Phil Sutter) [RHEL-190549] +- cache: Include chains, flowtables and objects in netlink debug output (Phil Sutter) [RHEL-190549] +- cache: Relax chain_cache_dump filter application (Phil Sutter) [RHEL-190549] +- parser_bison: add range check for synproxy wscale (Phil Sutter) [RHEL-190549] +- json: complete multi-statement set element support (Phil Sutter) [RHEL-190549] +- segtree: Fix range aggregation on Big Endian (Phil Sutter) [RHEL-190549] +- mergesort: Align concatenation sort order with Big Endian (Phil Sutter) [RHEL-190549] +- mergesort: Fix sorting of string values (Phil Sutter) [RHEL-190549] +- tests: py: any/tcpopt.t.json: Fix JSON equivalent (Phil Sutter) [RHEL-190549] +- expression: expr_build_udata_recurse should recurse (Phil Sutter) [RHEL-190549] +- expression: propagate key datatype for anonymous sets (Phil Sutter) [RHEL-190549] +- netlink_delinearize: also consider exthdr type when trimming binops (Phil Sutter) [RHEL-190549] +- tcpopt: add symbol table for mptcp suboptions (Phil Sutter) [RHEL-190549] +- monitor: fix memleak in setelem cb (Phil Sutter) [RHEL-190549] +- tests: json_echo: Drop rule handle before multi-add (Phil Sutter) [RHEL-190549] +- rule: skip CMD_OBJ_SETELEMS with no elements after set flush (Phil Sutter) [RHEL-190549] +- doc: libnftables-json: Describe RULESET object (Phil Sutter) [RHEL-190549] +- doc: don't suggest to disable GSO (Phil Sutter) [RHEL-190549] +- tests: py: Implement payload_record() (Phil Sutter) [RHEL-190549] +- tests: py: inet/osf.t: Fix element ordering in JSON equivalents (Phil Sutter) [RHEL-190549] +- tests: py: Do not rely upon '[end]' marker (Phil Sutter) [RHEL-190549] +- tests: py: Fix for using wrong payload path (Phil Sutter) [RHEL-190549] +- tests: py: any/ct.t.json.output: Drop leftover entry (Phil Sutter) [RHEL-190549] +- datatype: Fix boolean type on Big Endian (Phil Sutter) [RHEL-190549] +- optimize: Fix verdict expression comparison (Phil Sutter) [RHEL-190549] +- src: parser_json: fix format string bugs (Phil Sutter) [RHEL-190549] +- doc: fix tcpdump example (Phil Sutter) [RHEL-190549] +- tests: py: objects.t: must use input, not output (Phil Sutter) [RHEL-190549] +- fib: Fix for existence check on Big Endian (Phil Sutter) [RHEL-190549] +- tests: Prepare exit codes for automake (Phil Sutter) [RHEL-190549] +- monitor: Inform JSON printer when reporting an object delete event (Phil Sutter) [RHEL-190549] +- monitor: Recognize flowtable add/del events (Phil Sutter) [RHEL-190549] +- tests: monitor: Fix regex collecting expected echo output (Phil Sutter) [RHEL-190549] +- monitor: Quote device names in chain declarations, too (Phil Sutter) [RHEL-190549] +- mnl: continue on ENOBUFS errors when processing batch (Phil Sutter) [RHEL-190549] +- tests: monitor: Fix for flag arrays in JSON output (Phil Sutter) [RHEL-190549] +- mnl: silence compiler warning (Phil Sutter) [RHEL-190549] +- fib: restore JSON output for relational expressions (Phil Sutter) [RHEL-190549] +- src: ensure chain policy evaluation when specified (Phil Sutter) [RHEL-190549] +- segtree: incorrect type when aggregating concatenated set ranges (Phil Sutter) [RHEL-190549] +- json: Do not reduce single-item arrays on output (Phil Sutter) [RHEL-190549] +- tests: py: Fix tests added for 'icmpv6 taddr' support (Phil Sutter) [RHEL-190549] +- tests: py: Drop stale entry from ip/snat.t.payload (Phil Sutter) [RHEL-190549] +- tests: py: Drop stale entries from ip6/{ct,meta}.t.json (Phil Sutter) [RHEL-190549] +- tests: py: Drop stale entry from ip/snat.t.json (Phil Sutter) [RHEL-190549] +- tests: py: Drop duplicate test from inet/vxlan.t (Phil Sutter) [RHEL-190549] +- tests: py: Drop stale entry from inet/tcp.t.json (Phil Sutter) [RHEL-190549] +- tests: py: Drop duplicate test from inet/gretap.t (Phil Sutter) [RHEL-190549] +- tests: py: Drop duplicate test from inet/gre.t (Phil Sutter) [RHEL-190549] +- tests: py: Drop duplicate test from inet/geneve.t (Phil Sutter) [RHEL-190549] +- tests: py: Drop stale entries since redundant test case removal (Phil Sutter) [RHEL-190549] +- src: netlink: netlink_delinearize_table() may return NULL (Phil Sutter) [RHEL-190549] +- doc: nft.8: Minor NAT STATEMENTS section review (Phil Sutter) [RHEL-190549] +- mnl: Call mnl_attr_nest_end() just once (Phil Sutter) [RHEL-190549] +- evaluate: validate set expression type before accessing flags (Phil Sutter) [RHEL-190549] +- rule: print chain and flowtable devices in quotes (Phil Sutter) [RHEL-190549] +- tests: py: re-enables nft-test.py to load the local nftables.py (Phil Sutter) [RHEL-190549] +- fib: allow to use it in set statements (Phil Sutter) [RHEL-190549] +- fib: allow to check if route exists in maps (Phil Sutter) [RHEL-190549] +- tests: shell: Fix ifname_based_hooks feature check (Phil Sutter) [RHEL-190549] +- json: reject too long interface names (Phil Sutter) [RHEL-190549] +- tests/py: clean up set backend support fallout (Phil Sutter) [RHEL-190549] +- cache: assert name is non-nul when looking up (Phil Sutter) [RHEL-190549] +- parser_bison: only reset by name is supported by now (Phil Sutter) [RHEL-190549] +- rule: skip fuzzy lookup if object name is not available (Phil Sutter) [RHEL-190549] +- parser_bison: allow delete command with map via handle (Phil Sutter) [RHEL-190549] +- debug: include kernel set information on cache fill (Phil Sutter) [RHEL-190549] +- tests/py: prepare for set debug change (Phil Sutter) [RHEL-190549] +- src: BASECHAIN flag no longer implies presence of priority expression (Phil Sutter) [RHEL-190549] +- netlink: Avoid crash upon missing NFTNL_OBJ_CT_TIMEOUT_ARRAY attribute (Phil Sutter) [RHEL-190549] +- tests: py: Properly fix JSON equivalents for netdev/reject.t (Phil Sutter) [RHEL-190549] +- tests: shell: Adjust to ifname-based hooks (Phil Sutter) [RHEL-190549] +- tests: shell: combine dormant flag with netdevice removal (Phil Sutter) [RHEL-190549] +- tests: monitor: Fix for single flag array avoidance (Phil Sutter) [RHEL-190549] +- netlink: Do not allocate a bogus flowtable priority expr (Phil Sutter) [RHEL-190549] +- netlink: Fix for potential crash parsing a flowtable (Phil Sutter) [RHEL-190549] +- json: work around fuzzer-induced assert crashes (Phil Sutter) [RHEL-190549] +- json: prevent null deref if chain->policy is not set (Phil Sutter) [RHEL-190549] +- tests: py: fix json single-flag output for fib & synproxy (Phil Sutter) [RHEL-190549] +- tests: shell: check for features not available in 5.4 (Phil Sutter) [RHEL-190549] +- netlink: Avoid potential NULL-ptr deref parsing set elem expressions (Phil Sutter) [RHEL-190549] +- netlink: Catch unknown types when deserializing objects (Phil Sutter) [RHEL-190549] +- json: Introduce json_add_array_new() (Phil Sutter) [RHEL-190549] +- json: Fix for memleak in __binop_expr_json (Phil Sutter) [RHEL-190549] +- json: Accept more than two operands in binary expressions (Phil Sutter) [RHEL-190549] +- json: Print single fib flag as non-array (Phil Sutter) [RHEL-190549] +- tests: shell: Add test case for JSON 'flags' arrays (Phil Sutter) [RHEL-190549] +- json: Print single set flag as non-array (Phil Sutter) [RHEL-190549] +- json: Print single synproxy flags as non-array (Phil Sutter) [RHEL-190549] +- parser_json: Introduce parse_flags_array() (Phil Sutter) [RHEL-190549] +- doc: Fix typo in nat statement 'prefix' description (Phil Sutter) [RHEL-190549] +- netlink: bogus concatenated set ranges with netlink message overrun (Phil Sutter) [RHEL-190549] +- parser_bison: add selector_expr rule to restrict typeof_expr (Phil Sutter) [RHEL-190549] +- optimize: invalidate merge in case of duplicated key in set/map (Phil Sutter) [RHEL-190549] +- evaluate: bail out if ct saddr/daddr dependency cannot be inserted (Phil Sutter) [RHEL-190549] +- parser_json: bail out on malformed statement in set (Phil Sutter) [RHEL-190549] +- parser_json: reject empty jump/goto chain (Phil Sutter) [RHEL-190549] +- parser_json: allow statement stateful statement only in set elements (Phil Sutter) [RHEL-190549] +- cache: prevent possible crash rule filter is NULL (Phil Sutter) [RHEL-190549] +- optimize: expand expression list when merging into concatenation (Phil Sutter) [RHEL-190549] +- cache: don't crash when filter is NULL (Phil Sutter) [RHEL-190549] +- evaluate: only allow stateful statements in set and map definitions (Phil Sutter) [RHEL-190549] +- evaluate: compact STMT_F_STATEFUL checks (Phil Sutter) [RHEL-190549] +- json: don't BUG when asked to list synproxies (Phil Sutter) [RHEL-190549] +- optimize: incorrect comparison for reject statement (Phil Sutter) [RHEL-190549] +- optimize: compact bitmask matching in set/map (Phil Sutter) [RHEL-190549] +- tests: shell: missing ct count elements in new set_stmt test (Phil Sutter) [RHEL-190549] +- evaluate: don't update cache for anonymous chains (Phil Sutter) [RHEL-190549] +- json: make sure timeout list is initialised (Phil Sutter) [RHEL-190549] +- parser_bison: consolidate connlimit grammar rule for set elements (Phil Sutter) [RHEL-190549] +- parser_bison: consolidate last grammar rule for set elements (Phil Sutter) [RHEL-190549] +- parser_bison: consolidate quota grammar rule for set elements (Phil Sutter) [RHEL-190549] +- parser_bison: consolidate limit grammar rule for set elements (Phil Sutter) [RHEL-190549] +- parser_bison: consolidate counter grammar rule for set elements (Phil Sutter) [RHEL-190549] +- tests: shell: extend coverage for set element statements (Phil Sutter) [RHEL-190549] +- evaluate: fix assertion failure with malformed map definitions (Phil Sutter) [RHEL-190549] +- evaluate: don't allow nat map with specified protocol (Phil Sutter) [RHEL-190549] +- parser_bison: reject non-serializeable typeof expressions (Phil Sutter) [RHEL-190549] +- netlink: fix stack buffer overrun when emitting ranged expressions (Phil Sutter) [RHEL-190549] +- src: print set element with multi-word description in single one line (Phil Sutter) [RHEL-190549] +- tests: shell: detach synproxy test (Phil Sutter) [RHEL-190549] +- src: do not merge a set with a erroneous one (Phil Sutter) [RHEL-190549] +- segtree: incomplete output in get element command with maps (Phil Sutter) [RHEL-190549] +- evaluate: release existing datatype when evaluating unary expression (Phil Sutter) [RHEL-190549] +- segtree: fix string data initialisation (Phil Sutter) [RHEL-190549] +- payload: honor inner payload description in payload_expr_cmp() (Phil Sutter) [RHEL-190549] +- payload: return early if dependency is not a payload expression (Phil Sutter) [RHEL-190549] +- evaluate: optimize zero length range (Phil Sutter) [RHEL-190549] +- fib: Change data type of fib oifname to "ifname" (Phil Sutter) [RHEL-190549] +- evaluate: auto-merge is only available for singleton interval sets (Phil Sutter) [RHEL-190549] +- parser_bison: compact and simplify list and reset syntax (Phil Sutter) [RHEL-190549] +- parser_bison: turn redundant ip option type field match into boolean (Phil Sutter) [RHEL-190549] +- datatype: clamp boolean value to 0 and 1 (Phil Sutter) [RHEL-190549] +- tests: shell: delete netdev chain after test (Phil Sutter) [RHEL-190549] +- ipopt: use ipv4 address datatype for address field in ip options (Phil Sutter) [RHEL-190549] +- netlink_delinarize: fix bogus munging of mask value (Phil Sutter) [RHEL-190549] +- evaluate: remove variable shadowing (Phil Sutter) [RHEL-190549] +- intervals: do not merge intervals with different timeout (Phil Sutter) [RHEL-190549] +- src: add EXPR_RANGE_VALUE expression and use it (Phil Sutter) [RHEL-190549] +- intervals: add helper function to set previous element (Phil Sutter) [RHEL-190549] +- parser_bison: fix UaF when reporting table parse error (Phil Sutter) [RHEL-190549] +- intervals: set internal element location with the deletion trigger (Phil Sutter) [RHEL-190549] +- optimize: compare expression length (Phil Sutter) [RHEL-190549] +- tests: py: Fix for storing payload into missing file (Phil Sutter) [RHEL-190549] +- json: Support typeof in set and map types (Phil Sutter) [RHEL-190549] +- json: collapse set element commands from parser (Phil Sutter) [RHEL-190549] +- doc: extend description of fib expression (Phil Sutter) [RHEL-190549] +- tests: monitor: fix up test case breakage (Phil Sutter) [RHEL-190549] +- src: fix extended netlink error reporting with large set elements (Phil Sutter) [RHEL-190549] +- mnl: rename to mnl_seqnum_alloc() to mnl_seqnum_inc() (Phil Sutter) [RHEL-190549] +- mnl: update cmd_add_loc() to take struct nlmsghdr (Phil Sutter) [RHEL-190549] +- rule: netlink attribute offset is uint32_t for struct nlerr_loc (Phil Sutter) [RHEL-190549] +- src: collapse set element commands from parser (Phil Sutter) [RHEL-190549] +- libnftables-json: fix raw payload expression documentation (Phil Sutter) [RHEL-190549] +- cache: initialize filter when fetching implicit chains (Phil Sutter) [RHEL-190549] +- tests: py: fix up udp csum fixup output (Phil Sutter) [RHEL-190549] +- proto: use NFT_PAYLOAD_L4CSUM_PSEUDOHDR flag to mangle UDP checksum (Phil Sutter) [RHEL-190549] +- tests: shell: stabilize packetpath/payload (Phil Sutter) [RHEL-190549] +- libnftables: Zero ctx->vars after freeing it (Phil Sutter) [RHEL-190549] +- cache: position does not require full cache (Phil Sutter) [RHEL-190549] +- cache: relax requirement for replace rule command (Phil Sutter) [RHEL-190549] +- cache: remove full cache requirement when echo flag is set on (Phil Sutter) [RHEL-190549] +- cache: assert filter when calling nft_cache_evaluate() (Phil Sutter) [RHEL-190549] +- cache: consolidate reset command (Phil Sutter) [RHEL-190549] +- cache: add filtering support for objects (Phil Sutter) [RHEL-190549] +- cache: only dump rules for the given table (Phil Sutter) [RHEL-190549] +- cache: accumulate flags in batch (Phil Sutter) [RHEL-190549] +- cache: reset filter for each command (Phil Sutter) [RHEL-190549] +- parser_json: fix several expression memleaks from error path (Phil Sutter) [RHEL-190549] +- parser_json: release buffer returned by json_dumps (Phil Sutter) [RHEL-190549] +- json: Support maps with concatenated data (Phil Sutter) [RHEL-190549] +- parser_json: fix crash in json_parse_set_stmt_list (Phil Sutter) [RHEL-190549] +- parser_bison: allow 0 burst in limit rate byte mode (Phil Sutter) [RHEL-190549] +- datatype: improve error reporting when time unit is not correct (Phil Sutter) [RHEL-190549] +- cache: rule by index requires full cache (Phil Sutter) [RHEL-190549] +- datatype: reject rate in quota statement (Phil Sutter) [RHEL-190549] +- optimize: skip variables in nat statements (Phil Sutter) [RHEL-190549] +- parser_json: use stdin buffer if available (Phil Sutter) [RHEL-190549] +- libnftables: skip useable checks for /dev/stdin (Phil Sutter) [RHEL-190549] +- optimize: clone counter before insertion into set element (Phil Sutter) [RHEL-190549] +- segtree: set on EXPR_F_KERNEL flag for catchall elements in the cache (Phil Sutter) [RHEL-190549] +- evaluate: set on expr->len for catchall set elements (Phil Sutter) [RHEL-190549] +- parser_bison: recursive table declaration in deprecated meter statement (Phil Sutter) [RHEL-190549] +- intervals: fix element deletions with maps (Phil Sutter) [RHEL-190549] +- src: add string preprocessor and use it for log prefix string (Phil Sutter) [RHEL-190549] +- tests: shell: skip ip option tests if kernel does not support it (Phil Sutter) [RHEL-190549] +- cmd: skip variable set elements when collapsing commands (Phil Sutter) [RHEL-190549] +- cmd: provide better hint if chain is already declared with different type/hook/priority (Phil Sutter) [RHEL-190549] +- monitor: too large shift exponent displaying payload expression (Phil Sutter) [RHEL-190549] +- scanner: inet_pton() allows for broader IPv4-Mapped IPv6 addresses (Phil Sutter) [RHEL-190549] +- evaluate: Fix incorrect checking the `base` variable in case of IPV6 (Phil Sutter) [RHEL-190549] +- evaluate: bogus protocol conflicts in vlan with implicit dependencies (Phil Sutter) [RHEL-190549] +- cache: check for NFT_CACHE_REFRESH in current requested cache too (Phil Sutter) [RHEL-190549] +- doc: nft.8: Fix markup in ct expectation synopsis (Phil Sutter) [RHEL-190549] +- mergesort: Avoid accidental set element reordering (Phil Sutter) [RHEL-190549] +- doc: nft.8: Two minor synopsis fixups (Phil Sutter) [RHEL-190549] +- tests: shell: check for reset tcp options support (Phil Sutter) [RHEL-190549] +- tests: shell: payload matching requires egress support (Phil Sutter) [RHEL-190549] +- tests: py: complete icmp and icmpv6 update (Phil Sutter) [RHEL-190549] +- src: disentangle ICMP code types (Phil Sutter) [RHEL-190549] +- evaluate: display "Range negative size" error (Phil Sutter) [RHEL-190549] +- netlink_delinearize: restore binop syntax when listing ruleset for flags (Phil Sutter) [RHEL-190549] +- doc: libnftables-json: Drop invalid ops from match expression (Phil Sutter) [RHEL-190549] +- parser: json: Support for synproxy objects (Phil Sutter) [RHEL-190549] +- tests: py: add payload merging test cases (Phil Sutter) [RHEL-190549] +- nftables: do mot merge payloads on negation (Phil Sutter) [RHEL-190549] +- rule: fix ASAN errors in chain priority to textual names (Phil Sutter) [RHEL-190549] +- parser: compact type/typeof set rules (Phil Sutter) [RHEL-190549] +- parser: compact interval typeof rules (Phil Sutter) [RHEL-190549] +- src: improve error reporting for destroy command (Phil Sutter) [RHEL-190549] +- tests: shell: permit use of host-endian constant values in set lookup keys (Phil Sutter) [RHEL-190549] +- evaluate: permit use of host-endian constant values in set lookup keys (Phil Sutter) [RHEL-190549] +- expression: missing line in describe command with invalid expression (Phil Sutter) [RHEL-190549] +- netlink_delinearize: move concat and value postprocessing to helpers (Phil Sutter) [RHEL-190549] +- evaluate: skip byteorder conversion for selector smaller than 2 bytes (Phil Sutter) [RHEL-190549] +- cache: Optimize caching for 'list tables' command (Phil Sutter) [RHEL-190549] +- evaluate: fix check for unknown in cmd_op_to_name (Phil Sutter) [RHEL-190549] +- evaluate: don't assert on net/transport header conflict (Phil Sutter) [RHEL-190549] +- json: Support sets' auto-merge option (Phil Sutter) [RHEL-190549] +- rule: fix sym refcount assertion (Phil Sutter) [RHEL-190549] +- evaluate: error out when store needs more than one 128bit register of align fixup (Phil Sutter) [RHEL-190549] +- evaluate: do not fetch next expression on runaway number of concatenation components (Phil Sutter) [RHEL-190549] +- evaluate: skip anonymous set optimization for concatenations (Phil Sutter) [RHEL-190549] +- evaluate: add missing range checks for dup,fwd and payload statements (Phil Sutter) [RHEL-190549] +- doc: incorrect datatype description for icmpv6_type and icmpvx_code (Phil Sutter) [RHEL-190549] +- tests: shell: prefer project nft to system-wide nft (Phil Sutter) [RHEL-190549] +- parser_bison: ensure all timeout policy names are released (Phil Sutter) [RHEL-190549] +- netlink: fix stack overflow due to erroneous rounding (Phil Sutter) [RHEL-190549] +- parser_bison: error out on duplicated type/typeof/element keywords (Phil Sutter) [RHEL-190549] +- tests: shell: add test to cover payload transport match and mangle (Phil Sutter) [RHEL-190549] +- evaluate: fix stack overflow with huge priority string (Phil Sutter) [RHEL-190549] +- src: reject large raw payload and concat expressions (Phil Sutter) [RHEL-190549] +- evaluate: exthdr: statement arg must be not be a range (Phil Sutter) [RHEL-190549] +- meta: fix tc classid parsing out-of-bounds access (Phil Sutter) [RHEL-190549] +- parser_bison: close chain scope before chain release (Phil Sutter) [RHEL-190549] +- evaluate: fix bogus assertion failure with boolean datatype (Phil Sutter) [RHEL-190549] +- parser_bison: fix objref statement corruption (Phil Sutter) [RHEL-190549] +- tests: py: missing json output in meta.t with vlan mapping (Phil Sutter) [RHEL-190549] +- evaluate: reset statement length context before evaluating statement (Phil Sutter) [RHEL-190549] +- parser: tcpopt: fix tcp option parsing with NUM + length field (Phil Sutter) [RHEL-190549] +- evaluate: reject set definition with no key (Phil Sutter) [RHEL-190549] +- monitor: add support for concatenated set ranges (Phil Sutter) [RHEL-190549] +- evaluate: disable meta set with ranges (Phil Sutter) [RHEL-190549] +- evaluate: prevent assert when evaluating very large shift values (Phil Sutter) [RHEL-190549] +- evaluate: reject sets with no key (Phil Sutter) [RHEL-190549] +- evaluate: clone unary expression datatype to deal with dynamic datatype (Phil Sutter) [RHEL-190549] +- tests: shell: split nat inet tests (Phil Sutter) [RHEL-190549] +- evaluate: bogus error when adding devices to flowtable (Phil Sutter) [RHEL-190549] +- tests: shell: flush connlimit sets (Phil Sutter) [RHEL-190549] +- tests: shell: adjust add-after-delete flowtable for older kernels (Phil Sutter) [RHEL-190549] +- evaluate: fix rule replacement with anon sets (Phil Sutter) [RHEL-190549] +- tests: shell: skip if kernel does not support flowtable counter (Phil Sutter) [RHEL-190549] +- tests: shell: restore pipapo and chain binding coverage in standalone 30s-stress (Phil Sutter) [RHEL-190549] +- json: fix use after free in table_flags_json() (Phil Sutter) [RHEL-190549] +- src: expand create commands (Phil Sutter) [RHEL-190549] +- tests: shell: split set NAT interval test (Phil Sutter) [RHEL-190549] +- tests: shell: split merge nat optimization in two tests (Phil Sutter) [RHEL-190549] +- netlink: fix buffer size for user data in netlink_delinearize_chain() (Phil Sutter) [RHEL-190549] +- src: remove xfree() and use plain free() (Phil Sutter) [RHEL-190549] +- src: add free_const() and use it instead of xfree() (Phil Sutter) [RHEL-190549] +- evaluate: place byteorder conversion before rshift in payload expressions (Phil Sutter) [RHEL-190549] +- evaluate: reset statement length context only for set mappings (Phil Sutter) [RHEL-190549] +- meta: fix hour decoding when timezone offset is negative (Phil Sutter) [RHEL-190549] +- tproxy: Drop artificial port printing restriction (Phil Sutter) [RHEL-190549] +- tests/shell: fix mount command in "test-wrapper.sh" (Phil Sutter) [RHEL-190549] +- parser_bison: fix length check for ifname in ifname_expr_alloc() (Phil Sutter) [RHEL-190549] +- tests/shell: cover long interface name in "0042chain_variable_0" test (Phil Sutter) [RHEL-190549] +- tests/shell: add missing "elem_opts_compat_0.nodump" file (Phil Sutter) [RHEL-190549] +- parser_bison: Fix for broken compatibility with older dumps (Phil Sutter) [RHEL-190549] + * Wed Mar 18 2026 Phil Sutter [1.0.9-7.el9] - src: fix reset element support for interval set type (Phil Sutter) [RHEL-153461] - src: netlink: fix crash when ops doesn't support udata (Phil Sutter) [RHEL-153461]