* [nft PATCH v2 1/3] mnl: Support simple wildcards in netdev hooks
2025-07-15 15:15 [nft PATCH v2 0/3] Support wildcard netdev hooks Phil Sutter
@ 2025-07-15 15:15 ` Phil Sutter
2025-07-16 9:47 ` Florian Westphal
2025-07-15 15:15 ` [nft PATCH v2 2/3] parser_bison: Accept ASTERISK_STRING in flowtable_expr_member Phil Sutter
2025-07-15 15:15 ` [nft PATCH v2 3/3] tests: shell: Test ifname-based hooks Phil Sutter
2 siblings, 1 reply; 6+ messages in thread
From: Phil Sutter @ 2025-07-15 15:15 UTC (permalink / raw)
To: Pablo Neira Ayuso; +Cc: netfilter-devel
When building NFTA_FLOWTABLE_HOOK_DEVS, NFTA_HOOK_DEV or NFTA_HOOK_DEVS
attributes, detect trailing asterisks in interface names and reduce
attribute length accordingly. Kernel will use strncmp(), effectively
performing a prefix search this way.
Deserialization (i.e., appending asterisk to interface names which don't
include a trailing nul-char) happens in libnftnl.
Signed-off-by: Phil Sutter <phil@nwl.cc>
---
src/mnl.c | 19 ++++++++++++++-----
1 file changed, 14 insertions(+), 5 deletions(-)
diff --git a/src/mnl.c b/src/mnl.c
index 89bc742af3c5b..5abfd4b677cbd 100644
--- a/src/mnl.c
+++ b/src/mnl.c
@@ -799,18 +799,24 @@ static void mnl_nft_chain_devs_build(struct nlmsghdr *nlh, struct cmd *cmd)
{
const struct expr *dev_expr = cmd->chain->dev_expr;
const struct nft_dev *dev_array;
+ int i, len, num_devs = 0;
struct nlattr *nest_dev;
- int i, num_devs = 0;
dev_array = nft_dev_array(dev_expr, &num_devs);
if (num_devs == 1) {
cmd_add_loc(cmd, nlh, dev_array[0].location);
- mnl_attr_put_strz(nlh, NFTA_HOOK_DEV, dev_array[0].ifname);
+ len = strlen(dev_array[0].ifname) + 1;
+ if (dev_array[0].ifname[len - 2] == '*')
+ len -= 2;
+ mnl_attr_put(nlh, NFTA_HOOK_DEV, len, 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, dev_array[i].location);
- mnl_attr_put_strz(nlh, NFTA_DEVICE_NAME, dev_array[i].ifname);
+ len = strlen(dev_array[i].ifname) + 1;
+ if (dev_array[i].ifname[len - 2] == '*')
+ len -= 2;
+ mnl_attr_put(nlh, NFTA_DEVICE_NAME, len, dev_array[i].ifname);
mnl_attr_nest_end(nlh, nest_dev);
}
}
@@ -2084,14 +2090,17 @@ static void mnl_nft_ft_devs_build(struct nlmsghdr *nlh, struct cmd *cmd)
{
const struct expr *dev_expr = cmd->flowtable->dev_expr;
const struct nft_dev *dev_array;
+ int i, len, num_devs = 0;
struct nlattr *nest_dev;
- int i, num_devs= 0;
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, dev_array[i].location);
- mnl_attr_put_strz(nlh, NFTA_DEVICE_NAME, dev_array[i].ifname);
+ len = strlen(dev_array[i].ifname) + 1;
+ if (dev_array[i].ifname[len - 2] == '*')
+ len -= 2;
+ mnl_attr_put(nlh, NFTA_DEVICE_NAME, len, dev_array[i].ifname);
}
mnl_attr_nest_end(nlh, nest_dev);
--
2.49.0
^ permalink raw reply related [flat|nested] 6+ messages in thread* Re: [nft PATCH v2 1/3] mnl: Support simple wildcards in netdev hooks
2025-07-15 15:15 ` [nft PATCH v2 1/3] mnl: Support simple wildcards in " Phil Sutter
@ 2025-07-16 9:47 ` Florian Westphal
2025-07-16 10:26 ` Phil Sutter
0 siblings, 1 reply; 6+ messages in thread
From: Florian Westphal @ 2025-07-16 9:47 UTC (permalink / raw)
To: Phil Sutter; +Cc: Pablo Neira Ayuso, netfilter-devel
Phil Sutter <phil@nwl.cc> wrote:
> + len = strlen(dev_array[0].ifname) + 1;
> + if (dev_array[0].ifname[len - 2] == '*')
> + len -= 2;
Not obvious to me, is there a guarantee that 'len' is 2?
And, what if len yields 0 here?
> + if (dev_array[i].ifname[len - 2] == '*')
> + len -= 2;
> + mnl_attr_put(nlh, NFTA_DEVICE_NAME, len, dev_array[i].ifname);
> mnl_attr_nest_end(nlh, nest_dev);
> }
> }
> @@ -2084,14 +2090,17 @@ static void mnl_nft_ft_devs_build(struct nlmsghdr *nlh, struct cmd *cmd)
> {
> const struct expr *dev_expr = cmd->flowtable->dev_expr;
> const struct nft_dev *dev_array;
> + int i, len, num_devs = 0;
> struct nlattr *nest_dev;
> - int i, num_devs= 0;
>
> 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, dev_array[i].location);
> - mnl_attr_put_strz(nlh, NFTA_DEVICE_NAME, dev_array[i].ifname);
> + len = strlen(dev_array[i].ifname) + 1;
> + if (dev_array[i].ifname[len - 2] == '*')
> + len -= 2;
> + mnl_attr_put(nlh, NFTA_DEVICE_NAME, len, dev_array[i].ifname);
This (test, subtract, put) is a repeating pattern, perhaps this warrants a helper?
^ permalink raw reply [flat|nested] 6+ messages in thread* Re: [nft PATCH v2 1/3] mnl: Support simple wildcards in netdev hooks
2025-07-16 9:47 ` Florian Westphal
@ 2025-07-16 10:26 ` Phil Sutter
0 siblings, 0 replies; 6+ messages in thread
From: Phil Sutter @ 2025-07-16 10:26 UTC (permalink / raw)
To: Florian Westphal; +Cc: Pablo Neira Ayuso, netfilter-devel
On Wed, Jul 16, 2025 at 11:47:09AM +0200, Florian Westphal wrote:
> Phil Sutter <phil@nwl.cc> wrote:
> > + len = strlen(dev_array[0].ifname) + 1;
> > + if (dev_array[0].ifname[len - 2] == '*')
> > + len -= 2;
>
> Not obvious to me, is there a guarantee that 'len' is 2?
> And, what if len yields 0 here?
Oh, right. Same goes for the introduced libnftnl helpers.
> > + if (dev_array[i].ifname[len - 2] == '*')
> > + len -= 2;
> > + mnl_attr_put(nlh, NFTA_DEVICE_NAME, len, dev_array[i].ifname);
> > mnl_attr_nest_end(nlh, nest_dev);
> > }
> > }
> > @@ -2084,14 +2090,17 @@ static void mnl_nft_ft_devs_build(struct nlmsghdr *nlh, struct cmd *cmd)
> > {
> > const struct expr *dev_expr = cmd->flowtable->dev_expr;
> > const struct nft_dev *dev_array;
> > + int i, len, num_devs = 0;
> > struct nlattr *nest_dev;
> > - int i, num_devs= 0;
> >
> > 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, dev_array[i].location);
> > - mnl_attr_put_strz(nlh, NFTA_DEVICE_NAME, dev_array[i].ifname);
> > + len = strlen(dev_array[i].ifname) + 1;
> > + if (dev_array[i].ifname[len - 2] == '*')
> > + len -= 2;
> > + mnl_attr_put(nlh, NFTA_DEVICE_NAME, len, dev_array[i].ifname);
>
> This (test, subtract, put) is a repeating pattern, perhaps this warrants a helper?
It's even there already, (still) named 'mnl_attr_put_ifname' in my
libnftnl patch. I'll export it (with correct prefix) for use in
nftables.
Thanks, Phil
^ permalink raw reply [flat|nested] 6+ messages in thread
* [nft PATCH v2 2/3] parser_bison: Accept ASTERISK_STRING in flowtable_expr_member
2025-07-15 15:15 [nft PATCH v2 0/3] Support wildcard netdev hooks Phil Sutter
2025-07-15 15:15 ` [nft PATCH v2 1/3] mnl: Support simple wildcards in " Phil Sutter
@ 2025-07-15 15:15 ` Phil Sutter
2025-07-15 15:15 ` [nft PATCH v2 3/3] tests: shell: Test ifname-based hooks Phil Sutter
2 siblings, 0 replies; 6+ messages in thread
From: Phil Sutter @ 2025-07-15 15:15 UTC (permalink / raw)
To: Pablo Neira Ayuso; +Cc: netfilter-devel
All clauses are identical, so instead of adding a third one for
ASTERISK_STRING, use a single one for 'string' (which combines all three
variants).
Signed-off-by: Phil Sutter <phil@nwl.cc>
---
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 5b84331f220d3..c31fd05ec09cd 100644
--- a/src/parser_bison.y
+++ b/src/parser_bison.y
@@ -2472,16 +2472,7 @@ flowtable_list_expr : flowtable_expr_member
| flowtable_list_expr COMMA opt_newline
;
-flowtable_expr_member : QUOTED_STRING
- {
- struct expr *expr = ifname_expr_alloc(&@$, state->msgs, $1);
-
- if (!expr)
- YYERROR;
-
- $$ = expr;
- }
- | STRING
+flowtable_expr_member : string
{
struct expr *expr = ifname_expr_alloc(&@$, state->msgs, $1);
--
2.49.0
^ permalink raw reply related [flat|nested] 6+ messages in thread* [nft PATCH v2 3/3] tests: shell: Test ifname-based hooks
2025-07-15 15:15 [nft PATCH v2 0/3] Support wildcard netdev hooks Phil Sutter
2025-07-15 15:15 ` [nft PATCH v2 1/3] mnl: Support simple wildcards in " Phil Sutter
2025-07-15 15:15 ` [nft PATCH v2 2/3] parser_bison: Accept ASTERISK_STRING in flowtable_expr_member Phil Sutter
@ 2025-07-15 15:15 ` Phil Sutter
2 siblings, 0 replies; 6+ messages in thread
From: Phil Sutter @ 2025-07-15 15:15 UTC (permalink / raw)
To: Pablo Neira Ayuso; +Cc: netfilter-devel
Assert that:
- Non-matching interface specs are accepted
- Existing interfaces are hooked into upon flowtable/chain creation
- A new device matching the spec is hooked into immediately
- No stale hooks remain in 'nft list hooks' output
- Wildcard hooks basically work
Signed-off-by: Phil Sutter <phil@nwl.cc>
---
.../features/list_hooks_flowtable_info.sh | 7 +++
.../netdev_chain_name_based_hook_0.json-nft | 34 ++++++++++++++
.../dumps/netdev_chain_name_based_hook_0.nft | 5 +++
.../chains/netdev_chain_name_based_hook_0 | 44 ++++++++++++++++++
.../testcases/flowtable/0016name_based_hook_0 | 45 +++++++++++++++++++
.../dumps/0016name_based_hook_0.json-nft | 32 +++++++++++++
.../flowtable/dumps/0016name_based_hook_0.nft | 6 +++
7 files changed, 173 insertions(+)
create mode 100755 tests/shell/features/list_hooks_flowtable_info.sh
create mode 100644 tests/shell/testcases/chains/dumps/netdev_chain_name_based_hook_0.json-nft
create mode 100644 tests/shell/testcases/chains/dumps/netdev_chain_name_based_hook_0.nft
create mode 100755 tests/shell/testcases/chains/netdev_chain_name_based_hook_0
create mode 100755 tests/shell/testcases/flowtable/0016name_based_hook_0
create mode 100644 tests/shell/testcases/flowtable/dumps/0016name_based_hook_0.json-nft
create mode 100644 tests/shell/testcases/flowtable/dumps/0016name_based_hook_0.nft
diff --git a/tests/shell/features/list_hooks_flowtable_info.sh b/tests/shell/features/list_hooks_flowtable_info.sh
new file mode 100755
index 0000000000000..58bc57e040959
--- /dev/null
+++ b/tests/shell/features/list_hooks_flowtable_info.sh
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+# check for flowtable info in 'list hooks' output
+
+unshare -n bash -c " \
+$NFT \"table inet t { flowtable ft { hook ingress priority 0; devices = { lo }; }; }\"; \
+$NFT list hooks netdev device lo | grep -q flowtable\ inet\ t\ ft"
diff --git a/tests/shell/testcases/chains/dumps/netdev_chain_name_based_hook_0.json-nft b/tests/shell/testcases/chains/dumps/netdev_chain_name_based_hook_0.json-nft
new file mode 100644
index 0000000000000..00706271e96a4
--- /dev/null
+++ b/tests/shell/testcases/chains/dumps/netdev_chain_name_based_hook_0.json-nft
@@ -0,0 +1,34 @@
+{
+ "nftables": [
+ {
+ "metainfo": {
+ "version": "VERSION",
+ "release_name": "RELEASE_NAME",
+ "json_schema_version": 1
+ }
+ },
+ {
+ "table": {
+ "family": "netdev",
+ "name": "t",
+ "handle": 0
+ }
+ },
+ {
+ "chain": {
+ "family": "netdev",
+ "table": "t",
+ "name": "c",
+ "handle": 0,
+ "dev": [
+ "foo*",
+ "lo"
+ ],
+ "type": "filter",
+ "hook": "ingress",
+ "prio": 0,
+ "policy": "accept"
+ }
+ }
+ ]
+}
diff --git a/tests/shell/testcases/chains/dumps/netdev_chain_name_based_hook_0.nft b/tests/shell/testcases/chains/dumps/netdev_chain_name_based_hook_0.nft
new file mode 100644
index 0000000000000..ac5acacd12e6d
--- /dev/null
+++ b/tests/shell/testcases/chains/dumps/netdev_chain_name_based_hook_0.nft
@@ -0,0 +1,5 @@
+table netdev t {
+ chain c {
+ type filter hook ingress devices = { "foo*", "lo" } priority filter; policy accept;
+ }
+}
diff --git a/tests/shell/testcases/chains/netdev_chain_name_based_hook_0 b/tests/shell/testcases/chains/netdev_chain_name_based_hook_0
new file mode 100755
index 0000000000000..8a8a601784084
--- /dev/null
+++ b/tests/shell/testcases/chains/netdev_chain_name_based_hook_0
@@ -0,0 +1,44 @@
+#!/bin/bash
+
+# NFT_TEST_REQUIRES(NFT_TEST_HAVE_ifname_based_hooks)
+
+cspec=' chain netdev t c '
+$NFT add table netdev t
+$NFT add $cspec '{ type filter hook ingress priority 0; devices = { lo, foo* }; }'
+$NFT list hooks netdev device lo | grep -q "$cspec" || {
+ echo "Existing device lo not hooked into chain as expected"
+ exit 1
+}
+
+[[ $($NFT list hooks | grep -c "$cspec") -eq 1 ]] || {
+ echo "Chain hooks into more than just lo"
+ exit 2
+}
+
+ip link add foo1 type dummy
+$NFT list hooks netdev device foo1 | grep -q "$cspec" || {
+ echo "Chain did not hook into new device foo1"
+ exit 3
+}
+[[ $($NFT list hooks | grep -c "$cspec") -eq 2 ]] || {
+ echo "Chain expected to hook into exactly two devices"
+ exit 4
+}
+
+ip link del foo1
+$NFT list hooks netdev device foo1 | grep -q "$cspec" && {
+ echo "Chain still hooks into removed device foo1"
+ exit 5
+}
+[[ $($NFT list hooks | grep -c "$cspec") -eq 1 ]] || {
+ echo "Chain expected to hook into just lo"
+ exit 6
+}
+
+for ((i = 0; i < 100; i++)); do
+ ip link add foo$i type dummy
+done
+[[ $($NFT list hooks | grep -c "$cspec") -eq 101 ]] || {
+ echo "Chain did not hook into all 100 new devices"
+ exit 7
+}
diff --git a/tests/shell/testcases/flowtable/0016name_based_hook_0 b/tests/shell/testcases/flowtable/0016name_based_hook_0
new file mode 100755
index 0000000000000..9a55596027158
--- /dev/null
+++ b/tests/shell/testcases/flowtable/0016name_based_hook_0
@@ -0,0 +1,45 @@
+#!/bin/bash
+
+# NFT_TEST_REQUIRES(NFT_TEST_HAVE_ifname_based_hooks)
+# NFT_TEST_REQUIRES(NFT_TEST_HAVE_list_hooks_flowtable_info)
+
+ftspec=' flowtable ip t ft '
+$NFT add table t
+$NFT add $ftspec '{ hook ingress priority 0; devices = { lo, foo* }; }'
+$NFT list hooks netdev device lo | grep -q "$ftspec" || {
+ echo "Existing device lo not hooked into flowtable as expected"
+ exit 1
+}
+
+[[ $($NFT list hooks | grep -c "$ftspec") -eq 1 ]] || {
+ echo "Flowtable hooks into more than just lo"
+ exit 2
+}
+
+ip link add foo1 type dummy
+$NFT list hooks netdev device foo1 | grep -q "$ftspec" || {
+ echo "Flowtable did not hook into new device foo1"
+ exit 3
+}
+[[ $($NFT list hooks | grep -c "$ftspec") -eq 2 ]] || {
+ echo "Flowtable expected to hook into exactly two devices"
+ exit 4
+}
+
+ip link del foo1
+$NFT list hooks netdev device foo1 | grep -q "$ftspec" && {
+ echo "Flowtable still hooks into removed device foo1"
+ exit 5
+}
+[[ $($NFT list hooks | grep -c "$ftspec") -eq 1 ]] || {
+ echo "Flowtable expected to hook into just lo"
+ exit 6
+}
+
+for ((i = 0; i < 100; i++)); do
+ ip link add foo$i type dummy
+done
+[[ $($NFT list hooks | grep -c "$ftspec") -eq 101 ]] || {
+ echo "Flowtable did not hook into all 100 new devices"
+ exit 7
+}
diff --git a/tests/shell/testcases/flowtable/dumps/0016name_based_hook_0.json-nft b/tests/shell/testcases/flowtable/dumps/0016name_based_hook_0.json-nft
new file mode 100644
index 0000000000000..93e263323ff95
--- /dev/null
+++ b/tests/shell/testcases/flowtable/dumps/0016name_based_hook_0.json-nft
@@ -0,0 +1,32 @@
+{
+ "nftables": [
+ {
+ "metainfo": {
+ "version": "VERSION",
+ "release_name": "RELEASE_NAME",
+ "json_schema_version": 1
+ }
+ },
+ {
+ "table": {
+ "family": "ip",
+ "name": "t",
+ "handle": 0
+ }
+ },
+ {
+ "flowtable": {
+ "family": "ip",
+ "name": "ft",
+ "table": "t",
+ "handle": 0,
+ "hook": "ingress",
+ "prio": 0,
+ "dev": [
+ "foo*",
+ "lo"
+ ]
+ }
+ }
+ ]
+}
diff --git a/tests/shell/testcases/flowtable/dumps/0016name_based_hook_0.nft b/tests/shell/testcases/flowtable/dumps/0016name_based_hook_0.nft
new file mode 100644
index 0000000000000..b4810664a956f
--- /dev/null
+++ b/tests/shell/testcases/flowtable/dumps/0016name_based_hook_0.nft
@@ -0,0 +1,6 @@
+table ip t {
+ flowtable ft {
+ hook ingress priority filter
+ devices = { "foo*", "lo" }
+ }
+}
--
2.49.0
^ permalink raw reply related [flat|nested] 6+ messages in thread