* [nf-next PATCH v6 01/12] netfilter: nf_tables: Introduce functions freeing nft_hook objects
2025-04-15 15:44 [nf-next PATCH v6 00/12] Dynamic hook interface binding part 2 Phil Sutter
@ 2025-04-15 15:44 ` Phil Sutter
2025-04-15 15:44 ` [nf-next PATCH v6 02/12] netfilter: nf_tables: Introduce nft_hook_find_ops{,_rcu}() Phil Sutter
` (12 subsequent siblings)
13 siblings, 0 replies; 21+ messages in thread
From: Phil Sutter @ 2025-04-15 15:44 UTC (permalink / raw)
To: Pablo Neira Ayuso; +Cc: netfilter-devel, Florian Westphal, Eric Garver
Pointless wrappers around kfree() for now, prep work for an embedded
list of nf_hook_ops.
Signed-off-by: Phil Sutter <phil@nwl.cc>
---
Changes since v3:
- Move function declarations up and use the wrapper in
nft_netdev_unregister_hooks(), too.
---
net/netfilter/nf_tables_api.c | 38 ++++++++++++++++++++++-------------
1 file changed, 24 insertions(+), 14 deletions(-)
diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c
index a133e1c175ce..0e7cfbd3cf60 100644
--- a/net/netfilter/nf_tables_api.c
+++ b/net/netfilter/nf_tables_api.c
@@ -323,6 +323,16 @@ static int nft_netdev_register_hooks(struct net *net,
return err;
}
+static void nft_netdev_hook_free(struct nft_hook *hook)
+{
+ kfree(hook);
+}
+
+static void nft_netdev_hook_free_rcu(struct nft_hook *hook)
+{
+ kfree_rcu(hook, rcu);
+}
+
static void nft_netdev_unregister_hooks(struct net *net,
struct list_head *hook_list,
bool release_netdev)
@@ -333,7 +343,7 @@ static void nft_netdev_unregister_hooks(struct net *net,
nf_unregister_net_hook(net, &hook->ops);
if (release_netdev) {
list_del(&hook->list);
- kfree_rcu(hook, rcu);
+ nft_netdev_hook_free_rcu(hook);
}
}
}
@@ -2253,7 +2263,7 @@ void nf_tables_chain_destroy(struct nft_chain *chain)
list_for_each_entry_safe(hook, next,
&basechain->hook_list, list) {
list_del_rcu(&hook->list);
- kfree_rcu(hook, rcu);
+ nft_netdev_hook_free_rcu(hook);
}
}
module_put(basechain->type->owner);
@@ -2345,7 +2355,7 @@ static int nf_tables_parse_netdev_hooks(struct net *net,
}
if (nft_hook_list_find(hook_list, hook)) {
NL_SET_BAD_ATTR(extack, tmp);
- kfree(hook);
+ nft_netdev_hook_free(hook);
err = -EEXIST;
goto err_hook;
}
@@ -2363,7 +2373,7 @@ static int nf_tables_parse_netdev_hooks(struct net *net,
err_hook:
list_for_each_entry_safe(hook, next, hook_list, list) {
list_del(&hook->list);
- kfree(hook);
+ nft_netdev_hook_free(hook);
}
return err;
}
@@ -2506,7 +2516,7 @@ static void nft_chain_release_hook(struct nft_chain_hook *hook)
list_for_each_entry_safe(h, next, &hook->list, list) {
list_del(&h->list);
- kfree(h);
+ nft_netdev_hook_free(h);
}
module_put(hook->type->owner);
}
@@ -2795,7 +2805,7 @@ static int nf_tables_updchain(struct nft_ctx *ctx, u8 genmask, u8 policy,
if (nft_hook_list_find(&basechain->hook_list, h)) {
list_del(&h->list);
- kfree(h);
+ nft_netdev_hook_free(h);
}
}
} else {
@@ -2916,7 +2926,7 @@ static int nf_tables_updchain(struct nft_ctx *ctx, u8 genmask, u8 policy,
if (unregister)
nf_unregister_net_hook(ctx->net, &h->ops);
list_del(&h->list);
- kfree_rcu(h, rcu);
+ nft_netdev_hook_free_rcu(h);
}
module_put(hook.type->owner);
}
@@ -8881,7 +8891,7 @@ static void __nft_unregister_flowtable_net_hooks(struct net *net,
FLOW_BLOCK_UNBIND);
if (release_netdev) {
list_del(&hook->list);
- kfree_rcu(hook, rcu);
+ nft_netdev_hook_free_rcu(hook);
}
}
}
@@ -8939,7 +8949,7 @@ static int nft_register_flowtable_net_hooks(struct net *net,
nft_unregister_flowtable_hook(net, flowtable, hook);
list_del_rcu(&hook->list);
- kfree_rcu(hook, rcu);
+ nft_netdev_hook_free_rcu(hook);
}
return err;
@@ -8951,7 +8961,7 @@ static void nft_hooks_destroy(struct list_head *hook_list)
list_for_each_entry_safe(hook, next, hook_list, list) {
list_del_rcu(&hook->list);
- kfree_rcu(hook, rcu);
+ nft_netdev_hook_free_rcu(hook);
}
}
@@ -8975,7 +8985,7 @@ static int nft_flowtable_update(struct nft_ctx *ctx, const struct nlmsghdr *nlh,
list_for_each_entry_safe(hook, next, &flowtable_hook.list, list) {
if (nft_hook_list_find(&flowtable->hook_list, hook)) {
list_del(&hook->list);
- kfree(hook);
+ nft_netdev_hook_free(hook);
}
}
@@ -9022,7 +9032,7 @@ static int nft_flowtable_update(struct nft_ctx *ctx, const struct nlmsghdr *nlh,
if (unregister)
nft_unregister_flowtable_hook(ctx->net, flowtable, hook);
list_del_rcu(&hook->list);
- kfree_rcu(hook, rcu);
+ nft_netdev_hook_free_rcu(hook);
}
return err;
@@ -9168,7 +9178,7 @@ static void nft_flowtable_hook_release(struct nft_flowtable_hook *flowtable_hook
list_for_each_entry_safe(this, next, &flowtable_hook->list, list) {
list_del(&this->list);
- kfree(this);
+ nft_netdev_hook_free(this);
}
}
@@ -9531,7 +9541,7 @@ static void nf_tables_flowtable_destroy(struct nft_flowtable *flowtable)
flowtable->data.type->free(&flowtable->data);
list_for_each_entry_safe(hook, next, &flowtable->hook_list, list) {
list_del_rcu(&hook->list);
- kfree_rcu(hook, rcu);
+ nft_netdev_hook_free_rcu(hook);
}
kfree(flowtable->name);
module_put(flowtable->data.type->owner);
--
2.49.0
^ permalink raw reply related [flat|nested] 21+ messages in thread* [nf-next PATCH v6 02/12] netfilter: nf_tables: Introduce nft_hook_find_ops{,_rcu}()
2025-04-15 15:44 [nf-next PATCH v6 00/12] Dynamic hook interface binding part 2 Phil Sutter
2025-04-15 15:44 ` [nf-next PATCH v6 01/12] netfilter: nf_tables: Introduce functions freeing nft_hook objects Phil Sutter
@ 2025-04-15 15:44 ` Phil Sutter
2025-04-15 15:44 ` [nf-next PATCH v6 03/12] netfilter: nf_tables: Introduce nft_register_flowtable_ops() Phil Sutter
` (11 subsequent siblings)
13 siblings, 0 replies; 21+ messages in thread
From: Phil Sutter @ 2025-04-15 15:44 UTC (permalink / raw)
To: Pablo Neira Ayuso; +Cc: netfilter-devel, Florian Westphal, Eric Garver
Also a pretty dull wrapper around the hook->ops.dev comparison for now.
Will search the embedded nf_hook_ops list in future. The ugly cast to
eliminate the const qualifier will vanish then, too.
Since this future list will be RCU-protected, also introduce an _rcu()
variant here.
Signed-off-by: Phil Sutter <phil@nwl.cc>
---
Changes since v5:
- Add nft_hook_find_ops_rcu() now rather than later although it is
merely a wrapper around nft_hook_find_ops() for now.
---
include/net/netfilter/nf_tables.h | 5 +++++
net/netfilter/nf_tables_api.c | 21 ++++++++++++++++++++-
net/netfilter/nf_tables_offload.c | 2 +-
net/netfilter/nft_chain_filter.c | 6 ++++--
net/netfilter/nft_flow_offload.c | 2 +-
5 files changed, 31 insertions(+), 5 deletions(-)
diff --git a/include/net/netfilter/nf_tables.h b/include/net/netfilter/nf_tables.h
index 803d5f1601f9..df0b151743a2 100644
--- a/include/net/netfilter/nf_tables.h
+++ b/include/net/netfilter/nf_tables.h
@@ -1205,6 +1205,11 @@ struct nft_hook {
u8 ifnamelen;
};
+struct nf_hook_ops *nft_hook_find_ops(const struct nft_hook *hook,
+ const struct net_device *dev);
+struct nf_hook_ops *nft_hook_find_ops_rcu(const struct nft_hook *hook,
+ const struct net_device *dev);
+
/**
* struct nft_base_chain - nf_tables base chain
*
diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c
index 0e7cfbd3cf60..3e7a6b65177f 100644
--- a/net/netfilter/nf_tables_api.c
+++ b/net/netfilter/nf_tables_api.c
@@ -9574,13 +9574,32 @@ static int nf_tables_fill_gen_info(struct sk_buff *skb, struct net *net,
return -EMSGSIZE;
}
+struct nf_hook_ops *nft_hook_find_ops(const struct nft_hook *hook,
+ const struct net_device *dev)
+{
+ if (hook->ops.dev == dev)
+ return (struct nf_hook_ops *)&hook->ops;
+
+ return NULL;
+}
+EXPORT_SYMBOL_GPL(nft_hook_find_ops);
+
+struct nf_hook_ops *nft_hook_find_ops_rcu(const struct nft_hook *hook,
+ const struct net_device *dev)
+{
+ return nft_hook_find_ops(hook, dev);
+}
+EXPORT_SYMBOL_GPL(nft_hook_find_ops_rcu);
+
static void nft_flowtable_event(unsigned long event, struct net_device *dev,
struct nft_flowtable *flowtable)
{
+ struct nf_hook_ops *ops;
struct nft_hook *hook;
list_for_each_entry(hook, &flowtable->hook_list, list) {
- if (hook->ops.dev != dev)
+ ops = nft_hook_find_ops(hook, dev);
+ if (!ops)
continue;
/* flow_offload_netdev_event() cleans up entries for us. */
diff --git a/net/netfilter/nf_tables_offload.c b/net/netfilter/nf_tables_offload.c
index 64675f1c7f29..75b756f0b9f0 100644
--- a/net/netfilter/nf_tables_offload.c
+++ b/net/netfilter/nf_tables_offload.c
@@ -638,7 +638,7 @@ static struct nft_chain *__nft_offload_get_chain(const struct nftables_pernet *n
found = NULL;
basechain = nft_base_chain(chain);
list_for_each_entry(hook, &basechain->hook_list, list) {
- if (hook->ops.dev != dev)
+ if (!nft_hook_find_ops(hook, dev))
continue;
found = hook;
diff --git a/net/netfilter/nft_chain_filter.c b/net/netfilter/nft_chain_filter.c
index 19a553550c76..783e4b5ef3e0 100644
--- a/net/netfilter/nft_chain_filter.c
+++ b/net/netfilter/nft_chain_filter.c
@@ -321,14 +321,16 @@ static const struct nft_chain_type nft_chain_filter_netdev = {
static void nft_netdev_event(unsigned long event, struct net_device *dev,
struct nft_base_chain *basechain)
{
+ struct nf_hook_ops *ops;
struct nft_hook *hook;
list_for_each_entry(hook, &basechain->hook_list, list) {
- if (hook->ops.dev != dev)
+ ops = nft_hook_find_ops(hook, dev);
+ if (!ops)
continue;
if (!(basechain->chain.table->flags & NFT_TABLE_F_DORMANT))
- nf_unregister_net_hook(dev_net(dev), &hook->ops);
+ nf_unregister_net_hook(dev_net(dev), ops);
list_del_rcu(&hook->list);
kfree_rcu(hook, rcu);
diff --git a/net/netfilter/nft_flow_offload.c b/net/netfilter/nft_flow_offload.c
index 221d50223018..225ff293cd50 100644
--- a/net/netfilter/nft_flow_offload.c
+++ b/net/netfilter/nft_flow_offload.c
@@ -175,7 +175,7 @@ static bool nft_flowtable_find_dev(const struct net_device *dev,
bool found = false;
list_for_each_entry_rcu(hook, &ft->hook_list, list) {
- if (hook->ops.dev != dev)
+ if (!nft_hook_find_ops_rcu(hook, dev))
continue;
found = true;
--
2.49.0
^ permalink raw reply related [flat|nested] 21+ messages in thread* [nf-next PATCH v6 03/12] netfilter: nf_tables: Introduce nft_register_flowtable_ops()
2025-04-15 15:44 [nf-next PATCH v6 00/12] Dynamic hook interface binding part 2 Phil Sutter
2025-04-15 15:44 ` [nf-next PATCH v6 01/12] netfilter: nf_tables: Introduce functions freeing nft_hook objects Phil Sutter
2025-04-15 15:44 ` [nf-next PATCH v6 02/12] netfilter: nf_tables: Introduce nft_hook_find_ops{,_rcu}() Phil Sutter
@ 2025-04-15 15:44 ` Phil Sutter
2025-04-15 15:44 ` [nf-next PATCH v6 04/12] netfilter: nf_tables: Pass nf_hook_ops to nft_unregister_flowtable_hook() Phil Sutter
` (10 subsequent siblings)
13 siblings, 0 replies; 21+ messages in thread
From: Phil Sutter @ 2025-04-15 15:44 UTC (permalink / raw)
To: Pablo Neira Ayuso; +Cc: netfilter-devel, Florian Westphal, Eric Garver
Facilitate binding and registering of a flowtable hook via a single
function call.
Signed-off-by: Phil Sutter <phil@nwl.cc>
---
net/netfilter/nf_tables_api.c | 32 +++++++++++++++++++++-----------
1 file changed, 21 insertions(+), 11 deletions(-)
diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c
index 3e7a6b65177f..bc205114527a 100644
--- a/net/netfilter/nf_tables_api.c
+++ b/net/netfilter/nf_tables_api.c
@@ -8903,6 +8903,26 @@ static void nft_unregister_flowtable_net_hooks(struct net *net,
__nft_unregister_flowtable_net_hooks(net, flowtable, hook_list, false);
}
+static int nft_register_flowtable_ops(struct net *net,
+ struct nft_flowtable *flowtable,
+ struct nf_hook_ops *ops)
+{
+ int err;
+
+ err = flowtable->data.type->setup(&flowtable->data,
+ ops->dev, FLOW_BLOCK_BIND);
+ if (err < 0)
+ return err;
+
+ err = nf_register_net_hook(net, ops);
+ if (!err)
+ return 0;
+
+ flowtable->data.type->setup(&flowtable->data,
+ ops->dev, FLOW_BLOCK_UNBIND);
+ return err;
+}
+
static int nft_register_flowtable_net_hooks(struct net *net,
struct nft_table *table,
struct list_head *hook_list,
@@ -8923,20 +8943,10 @@ static int nft_register_flowtable_net_hooks(struct net *net,
}
}
- err = flowtable->data.type->setup(&flowtable->data,
- hook->ops.dev,
- FLOW_BLOCK_BIND);
+ err = nft_register_flowtable_ops(net, flowtable, &hook->ops);
if (err < 0)
goto err_unregister_net_hooks;
- err = nf_register_net_hook(net, &hook->ops);
- if (err < 0) {
- flowtable->data.type->setup(&flowtable->data,
- hook->ops.dev,
- FLOW_BLOCK_UNBIND);
- goto err_unregister_net_hooks;
- }
-
i++;
}
--
2.49.0
^ permalink raw reply related [flat|nested] 21+ messages in thread* [nf-next PATCH v6 04/12] netfilter: nf_tables: Pass nf_hook_ops to nft_unregister_flowtable_hook()
2025-04-15 15:44 [nf-next PATCH v6 00/12] Dynamic hook interface binding part 2 Phil Sutter
` (2 preceding siblings ...)
2025-04-15 15:44 ` [nf-next PATCH v6 03/12] netfilter: nf_tables: Introduce nft_register_flowtable_ops() Phil Sutter
@ 2025-04-15 15:44 ` Phil Sutter
2025-04-15 15:44 ` [nf-next PATCH v6 05/12] netfilter: nf_tables: Have a list of nf_hook_ops in nft_hook Phil Sutter
` (9 subsequent siblings)
13 siblings, 0 replies; 21+ messages in thread
From: Phil Sutter @ 2025-04-15 15:44 UTC (permalink / raw)
To: Pablo Neira Ayuso; +Cc: netfilter-devel, Florian Westphal, Eric Garver
The function accesses only the hook's ops field, pass it directly. This
prepares for nft_hooks holding a list of nf_hook_ops in future.
While at it, make use of the function in
__nft_unregister_flowtable_net_hooks() as well.
Signed-off-by: Phil Sutter <phil@nwl.cc>
---
Changes since v5:
- New patch.
---
net/netfilter/nf_tables_api.c | 20 +++++++++-----------
1 file changed, 9 insertions(+), 11 deletions(-)
diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c
index bc205114527a..f185432b7e90 100644
--- a/net/netfilter/nf_tables_api.c
+++ b/net/netfilter/nf_tables_api.c
@@ -8869,12 +8869,12 @@ nft_flowtable_type_get(struct net *net, u8 family)
}
/* Only called from error and netdev event paths. */
-static void nft_unregister_flowtable_hook(struct net *net,
- struct nft_flowtable *flowtable,
- struct nft_hook *hook)
+static void nft_unregister_flowtable_ops(struct net *net,
+ struct nft_flowtable *flowtable,
+ struct nf_hook_ops *ops)
{
- nf_unregister_net_hook(net, &hook->ops);
- flowtable->data.type->setup(&flowtable->data, hook->ops.dev,
+ nf_unregister_net_hook(net, ops);
+ flowtable->data.type->setup(&flowtable->data, ops->dev,
FLOW_BLOCK_UNBIND);
}
@@ -8886,9 +8886,7 @@ static void __nft_unregister_flowtable_net_hooks(struct net *net,
struct nft_hook *hook, *next;
list_for_each_entry_safe(hook, next, hook_list, list) {
- nf_unregister_net_hook(net, &hook->ops);
- flowtable->data.type->setup(&flowtable->data, hook->ops.dev,
- FLOW_BLOCK_UNBIND);
+ nft_unregister_flowtable_ops(net, flowtable, &hook->ops);
if (release_netdev) {
list_del(&hook->list);
nft_netdev_hook_free_rcu(hook);
@@ -8957,7 +8955,7 @@ static int nft_register_flowtable_net_hooks(struct net *net,
if (i-- <= 0)
break;
- nft_unregister_flowtable_hook(net, flowtable, hook);
+ nft_unregister_flowtable_ops(net, flowtable, &hook->ops);
list_del_rcu(&hook->list);
nft_netdev_hook_free_rcu(hook);
}
@@ -9040,7 +9038,7 @@ static int nft_flowtable_update(struct nft_ctx *ctx, const struct nlmsghdr *nlh,
err_flowtable_update_hook:
list_for_each_entry_safe(hook, next, &flowtable_hook.list, list) {
if (unregister)
- nft_unregister_flowtable_hook(ctx->net, flowtable, hook);
+ nft_unregister_flowtable_ops(ctx->net, flowtable, &hook->ops);
list_del_rcu(&hook->list);
nft_netdev_hook_free_rcu(hook);
}
@@ -9613,7 +9611,7 @@ static void nft_flowtable_event(unsigned long event, struct net_device *dev,
continue;
/* flow_offload_netdev_event() cleans up entries for us. */
- nft_unregister_flowtable_hook(dev_net(dev), flowtable, hook);
+ nft_unregister_flowtable_ops(dev_net(dev), flowtable, ops);
list_del_rcu(&hook->list);
kfree_rcu(hook, rcu);
break;
--
2.49.0
^ permalink raw reply related [flat|nested] 21+ messages in thread* [nf-next PATCH v6 05/12] netfilter: nf_tables: Have a list of nf_hook_ops in nft_hook
2025-04-15 15:44 [nf-next PATCH v6 00/12] Dynamic hook interface binding part 2 Phil Sutter
` (3 preceding siblings ...)
2025-04-15 15:44 ` [nf-next PATCH v6 04/12] netfilter: nf_tables: Pass nf_hook_ops to nft_unregister_flowtable_hook() Phil Sutter
@ 2025-04-15 15:44 ` Phil Sutter
2025-04-15 15:44 ` [nf-next PATCH v6 06/12] netfilter: nf_tables: Prepare for handling NETDEV_REGISTER events Phil Sutter
` (8 subsequent siblings)
13 siblings, 0 replies; 21+ messages in thread
From: Phil Sutter @ 2025-04-15 15:44 UTC (permalink / raw)
To: Pablo Neira Ayuso; +Cc: netfilter-devel, Florian Westphal, Eric Garver
Supporting a 1:n relationship between nft_hook and nf_hook_ops is
convenient since a chain's or flowtable's nft_hooks may remain in place
despite matching interfaces disappearing. This stabilizes ruleset dumps
in that regard and opens the possibility to claim newly added interfaces
which match the spec. Also it prepares for wildcard interface specs
since these will potentially match multiple interfaces.
All spots dealing with hook registration are updated to handle a list of
multiple nf_hook_ops, but nft_netdev_hook_alloc() only adds a single
item for now to retain the old behaviour. The only expected functional
change here is how vanishing interfaces are handled: Instead of dropping
the respective nft_hook, only the matching nf_hook_ops are dropped.
To safely remove individual ops from the list in netdev handlers, an
rcu_head is added to struct nf_hook_ops so kfree_rcu() may be used.
There is at least nft_flowtable_find_dev() which may be iterating
through the list at the same time.
Signed-off-by: Phil Sutter <phil@nwl.cc>
---
Changes since v3:
- Add an rcu_head to nf_hook_ops.
- RCU-free an nft_hook along with its ops_list via call_rcu() and a
callback.
---
include/linux/netfilter.h | 3 +
include/net/netfilter/nf_tables.h | 2 +-
net/netfilter/nf_tables_api.c | 142 ++++++++++++++++++++++--------
net/netfilter/nf_tables_offload.c | 49 ++++++-----
net/netfilter/nft_chain_filter.c | 5 +-
5 files changed, 137 insertions(+), 64 deletions(-)
diff --git a/include/linux/netfilter.h b/include/linux/netfilter.h
index 2b8aac2c70ad..949ced7d58f5 100644
--- a/include/linux/netfilter.h
+++ b/include/linux/netfilter.h
@@ -95,6 +95,9 @@ enum nf_hook_ops_type {
};
struct nf_hook_ops {
+ struct list_head list;
+ struct rcu_head rcu;
+
/* User fills in from here down. */
nf_hookfn *hook;
struct net_device *dev;
diff --git a/include/net/netfilter/nf_tables.h b/include/net/netfilter/nf_tables.h
index df0b151743a2..5e49619ae49c 100644
--- a/include/net/netfilter/nf_tables.h
+++ b/include/net/netfilter/nf_tables.h
@@ -1199,7 +1199,7 @@ struct nft_stats {
struct nft_hook {
struct list_head list;
- struct nf_hook_ops ops;
+ struct list_head ops_list;
struct rcu_head rcu;
char ifname[IFNAMSIZ];
u8 ifnamelen;
diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c
index f185432b7e90..fa439ecfca15 100644
--- a/net/netfilter/nf_tables_api.c
+++ b/net/netfilter/nf_tables_api.c
@@ -300,47 +300,72 @@ void nf_tables_unbind_chain(const struct nft_ctx *ctx, struct nft_chain *chain)
static int nft_netdev_register_hooks(struct net *net,
struct list_head *hook_list)
{
+ struct nf_hook_ops *ops;
struct nft_hook *hook;
int err, j;
j = 0;
list_for_each_entry(hook, hook_list, list) {
- err = nf_register_net_hook(net, &hook->ops);
- if (err < 0)
- goto err_register;
+ list_for_each_entry(ops, &hook->ops_list, list) {
+ err = nf_register_net_hook(net, ops);
+ if (err < 0)
+ goto err_register;
- j++;
+ j++;
+ }
}
return 0;
err_register:
list_for_each_entry(hook, hook_list, list) {
- if (j-- <= 0)
- break;
+ list_for_each_entry(ops, &hook->ops_list, list) {
+ if (j-- <= 0)
+ break;
- nf_unregister_net_hook(net, &hook->ops);
+ nf_unregister_net_hook(net, ops);
+ }
}
return err;
}
+static void nft_netdev_hook_free_ops(struct nft_hook *hook)
+{
+ struct nf_hook_ops *ops, *next;
+
+ list_for_each_entry_safe(ops, next, &hook->ops_list, list) {
+ list_del(&ops->list);
+ kfree(ops);
+ }
+}
+
static void nft_netdev_hook_free(struct nft_hook *hook)
{
+ nft_netdev_hook_free_ops(hook);
kfree(hook);
}
+static void __nft_netdev_hook_free_rcu(struct rcu_head *rcu)
+{
+ struct nft_hook *hook = container_of(rcu, struct nft_hook, rcu);
+
+ nft_netdev_hook_free(hook);
+}
+
static void nft_netdev_hook_free_rcu(struct nft_hook *hook)
{
- kfree_rcu(hook, rcu);
+ call_rcu(&hook->rcu, __nft_netdev_hook_free_rcu);
}
static void nft_netdev_unregister_hooks(struct net *net,
struct list_head *hook_list,
bool release_netdev)
{
+ struct nf_hook_ops *ops, *nextops;
struct nft_hook *hook, *next;
list_for_each_entry_safe(hook, next, hook_list, list) {
- nf_unregister_net_hook(net, &hook->ops);
+ list_for_each_entry_safe(ops, nextops, &hook->ops_list, list)
+ nf_unregister_net_hook(net, ops);
if (release_netdev) {
list_del(&hook->list);
nft_netdev_hook_free_rcu(hook);
@@ -2284,6 +2309,7 @@ void nf_tables_chain_destroy(struct nft_chain *chain)
static struct nft_hook *nft_netdev_hook_alloc(struct net *net,
const struct nlattr *attr)
{
+ struct nf_hook_ops *ops;
struct net_device *dev;
struct nft_hook *hook;
int err;
@@ -2293,6 +2319,7 @@ static struct nft_hook *nft_netdev_hook_alloc(struct net *net,
err = -ENOMEM;
goto err_hook_alloc;
}
+ INIT_LIST_HEAD(&hook->ops_list);
err = nla_strscpy(hook->ifname, attr, IFNAMSIZ);
if (err < 0)
@@ -2309,7 +2336,14 @@ static struct nft_hook *nft_netdev_hook_alloc(struct net *net,
err = -ENOENT;
goto err_hook_dev;
}
- hook->ops.dev = dev;
+
+ ops = kzalloc(sizeof(struct nf_hook_ops), GFP_KERNEL_ACCOUNT);
+ if (!ops) {
+ err = -ENOMEM;
+ goto err_hook_dev;
+ }
+ ops->dev = dev;
+ list_add_tail(&ops->list, &hook->ops_list);
return hook;
@@ -2569,6 +2603,7 @@ static int nft_basechain_init(struct nft_base_chain *basechain, u8 family,
struct nft_chain_hook *hook, u32 flags)
{
struct nft_chain *chain;
+ struct nf_hook_ops *ops;
struct nft_hook *h;
basechain->type = hook->type;
@@ -2577,8 +2612,10 @@ static int nft_basechain_init(struct nft_base_chain *basechain, u8 family,
if (nft_base_chain_netdev(family, hook->num)) {
list_splice_init(&hook->list, &basechain->hook_list);
- list_for_each_entry(h, &basechain->hook_list, list)
- nft_basechain_hook_init(&h->ops, family, hook, chain);
+ list_for_each_entry(h, &basechain->hook_list, list) {
+ list_for_each_entry(ops, &h->ops_list, list)
+ nft_basechain_hook_init(ops, family, hook, chain);
+ }
}
nft_basechain_hook_init(&basechain->ops, family, hook, chain);
@@ -2797,11 +2834,13 @@ static int nf_tables_updchain(struct nft_ctx *ctx, u8 genmask, u8 policy,
if (nft_base_chain_netdev(ctx->family, basechain->ops.hooknum)) {
list_for_each_entry_safe(h, next, &hook.list, list) {
- h->ops.pf = basechain->ops.pf;
- h->ops.hooknum = basechain->ops.hooknum;
- h->ops.priority = basechain->ops.priority;
- h->ops.priv = basechain->ops.priv;
- h->ops.hook = basechain->ops.hook;
+ list_for_each_entry(ops, &h->ops_list, list) {
+ ops->pf = basechain->ops.pf;
+ ops->hooknum = basechain->ops.hooknum;
+ ops->priority = basechain->ops.priority;
+ ops->priv = basechain->ops.priv;
+ ops->hook = basechain->ops.hook;
+ }
if (nft_hook_list_find(&basechain->hook_list, h)) {
list_del(&h->list);
@@ -2923,8 +2962,10 @@ static int nf_tables_updchain(struct nft_ctx *ctx, u8 genmask, u8 policy,
err_hooks:
if (nla[NFTA_CHAIN_HOOK]) {
list_for_each_entry_safe(h, next, &hook.list, list) {
- if (unregister)
- nf_unregister_net_hook(ctx->net, &h->ops);
+ if (unregister) {
+ list_for_each_entry(ops, &h->ops_list, list)
+ nf_unregister_net_hook(ctx->net, ops);
+ }
list_del(&h->list);
nft_netdev_hook_free_rcu(h);
}
@@ -8769,6 +8810,7 @@ static int nft_flowtable_parse_hook(const struct nft_ctx *ctx,
struct netlink_ext_ack *extack, bool add)
{
struct nlattr *tb[NFTA_FLOWTABLE_HOOK_MAX + 1];
+ struct nf_hook_ops *ops;
struct nft_hook *hook;
int hooknum, priority;
int err;
@@ -8823,11 +8865,13 @@ static int nft_flowtable_parse_hook(const struct nft_ctx *ctx,
}
list_for_each_entry(hook, &flowtable_hook->list, list) {
- hook->ops.pf = NFPROTO_NETDEV;
- hook->ops.hooknum = flowtable_hook->num;
- hook->ops.priority = flowtable_hook->priority;
- hook->ops.priv = &flowtable->data;
- hook->ops.hook = flowtable->data.type->hook;
+ list_for_each_entry(ops, &hook->ops_list, list) {
+ ops->pf = NFPROTO_NETDEV;
+ ops->hooknum = flowtable_hook->num;
+ ops->priority = flowtable_hook->priority;
+ ops->priv = &flowtable->data;
+ ops->hook = flowtable->data.type->hook;
+ }
}
return err;
@@ -8884,9 +8928,11 @@ static void __nft_unregister_flowtable_net_hooks(struct net *net,
bool release_netdev)
{
struct nft_hook *hook, *next;
+ struct nf_hook_ops *ops;
list_for_each_entry_safe(hook, next, hook_list, list) {
- nft_unregister_flowtable_ops(net, flowtable, &hook->ops);
+ list_for_each_entry(ops, &hook->ops_list, list)
+ nft_unregister_flowtable_ops(net, flowtable, ops);
if (release_netdev) {
list_del(&hook->list);
nft_netdev_hook_free_rcu(hook);
@@ -8928,6 +8974,7 @@ static int nft_register_flowtable_net_hooks(struct net *net,
{
struct nft_hook *hook, *next;
struct nft_flowtable *ft;
+ struct nf_hook_ops *ops;
int err, i = 0;
list_for_each_entry(hook, hook_list, list) {
@@ -8941,21 +8988,25 @@ static int nft_register_flowtable_net_hooks(struct net *net,
}
}
- err = nft_register_flowtable_ops(net, flowtable, &hook->ops);
- if (err < 0)
- goto err_unregister_net_hooks;
+ list_for_each_entry(ops, &hook->ops_list, list) {
+ err = nft_register_flowtable_ops(net, flowtable, ops);
+ if (err < 0)
+ goto err_unregister_net_hooks;
- i++;
+ i++;
+ }
}
return 0;
err_unregister_net_hooks:
list_for_each_entry_safe(hook, next, hook_list, list) {
- if (i-- <= 0)
- break;
+ list_for_each_entry(ops, &hook->ops_list, list) {
+ if (i-- <= 0)
+ break;
- nft_unregister_flowtable_ops(net, flowtable, &hook->ops);
+ nft_unregister_flowtable_ops(net, flowtable, ops);
+ }
list_del_rcu(&hook->list);
nft_netdev_hook_free_rcu(hook);
}
@@ -8980,6 +9031,7 @@ static int nft_flowtable_update(struct nft_ctx *ctx, const struct nlmsghdr *nlh,
const struct nlattr * const *nla = ctx->nla;
struct nft_flowtable_hook flowtable_hook;
struct nft_hook *hook, *next;
+ struct nf_hook_ops *ops;
struct nft_trans *trans;
bool unregister = false;
u32 flags;
@@ -9037,8 +9089,11 @@ static int nft_flowtable_update(struct nft_ctx *ctx, const struct nlmsghdr *nlh,
err_flowtable_update_hook:
list_for_each_entry_safe(hook, next, &flowtable_hook.list, list) {
- if (unregister)
- nft_unregister_flowtable_ops(ctx->net, flowtable, &hook->ops);
+ if (unregister) {
+ list_for_each_entry(ops, &hook->ops_list, list)
+ nft_unregister_flowtable_ops(ctx->net,
+ flowtable, ops);
+ }
list_del_rcu(&hook->list);
nft_netdev_hook_free_rcu(hook);
}
@@ -9585,9 +9640,12 @@ static int nf_tables_fill_gen_info(struct sk_buff *skb, struct net *net,
struct nf_hook_ops *nft_hook_find_ops(const struct nft_hook *hook,
const struct net_device *dev)
{
- if (hook->ops.dev == dev)
- return (struct nf_hook_ops *)&hook->ops;
+ struct nf_hook_ops *ops;
+ list_for_each_entry(ops, &hook->ops_list, list) {
+ if (ops->dev == dev)
+ return ops;
+ }
return NULL;
}
EXPORT_SYMBOL_GPL(nft_hook_find_ops);
@@ -9595,7 +9653,13 @@ EXPORT_SYMBOL_GPL(nft_hook_find_ops);
struct nf_hook_ops *nft_hook_find_ops_rcu(const struct nft_hook *hook,
const struct net_device *dev)
{
- return nft_hook_find_ops(hook, dev);
+ struct nf_hook_ops *ops;
+
+ list_for_each_entry_rcu(ops, &hook->ops_list, list) {
+ if (ops->dev == dev)
+ return ops;
+ }
+ return NULL;
}
EXPORT_SYMBOL_GPL(nft_hook_find_ops_rcu);
@@ -9612,8 +9676,8 @@ static void nft_flowtable_event(unsigned long event, struct net_device *dev,
/* flow_offload_netdev_event() cleans up entries for us. */
nft_unregister_flowtable_ops(dev_net(dev), flowtable, ops);
- list_del_rcu(&hook->list);
- kfree_rcu(hook, rcu);
+ list_del_rcu(&ops->list);
+ kfree_rcu(ops, rcu);
break;
}
}
diff --git a/net/netfilter/nf_tables_offload.c b/net/netfilter/nf_tables_offload.c
index 75b756f0b9f0..fd30e205de84 100644
--- a/net/netfilter/nf_tables_offload.c
+++ b/net/netfilter/nf_tables_offload.c
@@ -220,6 +220,7 @@ static int nft_chain_offload_priority(const struct nft_base_chain *basechain)
bool nft_chain_offload_support(const struct nft_base_chain *basechain)
{
+ struct nf_hook_ops *ops;
struct net_device *dev;
struct nft_hook *hook;
@@ -227,13 +228,16 @@ bool nft_chain_offload_support(const struct nft_base_chain *basechain)
return false;
list_for_each_entry(hook, &basechain->hook_list, list) {
- if (hook->ops.pf != NFPROTO_NETDEV ||
- hook->ops.hooknum != NF_NETDEV_INGRESS)
- return false;
-
- dev = hook->ops.dev;
- if (!dev->netdev_ops->ndo_setup_tc && !flow_indr_dev_exists())
- return false;
+ list_for_each_entry(ops, &hook->ops_list, list) {
+ if (ops->pf != NFPROTO_NETDEV ||
+ ops->hooknum != NF_NETDEV_INGRESS)
+ return false;
+
+ dev = ops->dev;
+ if (!dev->netdev_ops->ndo_setup_tc &&
+ !flow_indr_dev_exists())
+ return false;
+ }
}
return true;
@@ -455,34 +459,37 @@ static int nft_flow_block_chain(struct nft_base_chain *basechain,
const struct net_device *this_dev,
enum flow_block_command cmd)
{
- struct net_device *dev;
+ struct nf_hook_ops *ops;
struct nft_hook *hook;
int err, i = 0;
list_for_each_entry(hook, &basechain->hook_list, list) {
- dev = hook->ops.dev;
- if (this_dev && this_dev != dev)
- continue;
+ list_for_each_entry(ops, &hook->ops_list, list) {
+ if (this_dev && this_dev != ops->dev)
+ continue;
- err = nft_chain_offload_cmd(basechain, dev, cmd);
- if (err < 0 && cmd == FLOW_BLOCK_BIND) {
- if (!this_dev)
- goto err_flow_block;
+ err = nft_chain_offload_cmd(basechain, ops->dev, cmd);
+ if (err < 0 && cmd == FLOW_BLOCK_BIND) {
+ if (!this_dev)
+ goto err_flow_block;
- return err;
+ return err;
+ }
+ i++;
}
- i++;
}
return 0;
err_flow_block:
list_for_each_entry(hook, &basechain->hook_list, list) {
- if (i-- <= 0)
- break;
+ list_for_each_entry(ops, &hook->ops_list, list) {
+ if (i-- <= 0)
+ break;
- dev = hook->ops.dev;
- nft_chain_offload_cmd(basechain, dev, FLOW_BLOCK_UNBIND);
+ nft_chain_offload_cmd(basechain, ops->dev,
+ FLOW_BLOCK_UNBIND);
+ }
}
return err;
}
diff --git a/net/netfilter/nft_chain_filter.c b/net/netfilter/nft_chain_filter.c
index 783e4b5ef3e0..bac5aa8970a4 100644
--- a/net/netfilter/nft_chain_filter.c
+++ b/net/netfilter/nft_chain_filter.c
@@ -332,9 +332,8 @@ static void nft_netdev_event(unsigned long event, struct net_device *dev,
if (!(basechain->chain.table->flags & NFT_TABLE_F_DORMANT))
nf_unregister_net_hook(dev_net(dev), ops);
- list_del_rcu(&hook->list);
- kfree_rcu(hook, rcu);
- break;
+ list_del_rcu(&ops->list);
+ kfree_rcu(ops, rcu);
}
}
--
2.49.0
^ permalink raw reply related [flat|nested] 21+ messages in thread* [nf-next PATCH v6 06/12] netfilter: nf_tables: Prepare for handling NETDEV_REGISTER events
2025-04-15 15:44 [nf-next PATCH v6 00/12] Dynamic hook interface binding part 2 Phil Sutter
` (4 preceding siblings ...)
2025-04-15 15:44 ` [nf-next PATCH v6 05/12] netfilter: nf_tables: Have a list of nf_hook_ops in nft_hook Phil Sutter
@ 2025-04-15 15:44 ` Phil Sutter
2025-04-15 15:44 ` [nf-next PATCH v6 07/12] netfilter: nf_tables: Respect " Phil Sutter
` (7 subsequent siblings)
13 siblings, 0 replies; 21+ messages in thread
From: Phil Sutter @ 2025-04-15 15:44 UTC (permalink / raw)
To: Pablo Neira Ayuso; +Cc: netfilter-devel, Florian Westphal, Eric Garver
Put NETDEV_UNREGISTER handling code into a switch, no functional change
intended as the function is only called for that event yet.
Signed-off-by: Phil Sutter <phil@nwl.cc>
---
Changes since v5:
- New patch.
---
net/netfilter/nf_tables_api.c | 19 ++++++++++++-------
net/netfilter/nft_chain_filter.c | 19 ++++++++++++-------
2 files changed, 24 insertions(+), 14 deletions(-)
diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c
index fa439ecfca15..523dab9b8ef4 100644
--- a/net/netfilter/nf_tables_api.c
+++ b/net/netfilter/nf_tables_api.c
@@ -9670,14 +9670,19 @@ static void nft_flowtable_event(unsigned long event, struct net_device *dev,
struct nft_hook *hook;
list_for_each_entry(hook, &flowtable->hook_list, list) {
- ops = nft_hook_find_ops(hook, dev);
- if (!ops)
- continue;
+ switch (event) {
+ case NETDEV_UNREGISTER:
+ ops = nft_hook_find_ops(hook, dev);
+ if (!ops)
+ continue;
- /* flow_offload_netdev_event() cleans up entries for us. */
- nft_unregister_flowtable_ops(dev_net(dev), flowtable, ops);
- list_del_rcu(&ops->list);
- kfree_rcu(ops, rcu);
+ /* flow_offload_netdev_event() cleans up entries for us. */
+ nft_unregister_flowtable_ops(dev_net(dev),
+ flowtable, ops);
+ list_del_rcu(&ops->list);
+ kfree_rcu(ops, rcu);
+ break;
+ }
break;
}
}
diff --git a/net/netfilter/nft_chain_filter.c b/net/netfilter/nft_chain_filter.c
index bac5aa8970a4..d22a708e63c8 100644
--- a/net/netfilter/nft_chain_filter.c
+++ b/net/netfilter/nft_chain_filter.c
@@ -321,19 +321,24 @@ static const struct nft_chain_type nft_chain_filter_netdev = {
static void nft_netdev_event(unsigned long event, struct net_device *dev,
struct nft_base_chain *basechain)
{
+ struct nft_table *table = basechain->chain.table;
struct nf_hook_ops *ops;
struct nft_hook *hook;
list_for_each_entry(hook, &basechain->hook_list, list) {
- ops = nft_hook_find_ops(hook, dev);
- if (!ops)
- continue;
+ switch (event) {
+ case NETDEV_UNREGISTER:
+ ops = nft_hook_find_ops(hook, dev);
+ if (!ops)
+ continue;
- if (!(basechain->chain.table->flags & NFT_TABLE_F_DORMANT))
- nf_unregister_net_hook(dev_net(dev), ops);
+ if (!(table->flags & NFT_TABLE_F_DORMANT))
+ nf_unregister_net_hook(dev_net(dev), ops);
- list_del_rcu(&ops->list);
- kfree_rcu(ops, rcu);
+ list_del_rcu(&ops->list);
+ kfree_rcu(ops, rcu);
+ break;
+ }
}
}
--
2.49.0
^ permalink raw reply related [flat|nested] 21+ messages in thread* [nf-next PATCH v6 07/12] netfilter: nf_tables: Respect NETDEV_REGISTER events
2025-04-15 15:44 [nf-next PATCH v6 00/12] Dynamic hook interface binding part 2 Phil Sutter
` (5 preceding siblings ...)
2025-04-15 15:44 ` [nf-next PATCH v6 06/12] netfilter: nf_tables: Prepare for handling NETDEV_REGISTER events Phil Sutter
@ 2025-04-15 15:44 ` Phil Sutter
2025-04-15 15:44 ` [nf-next PATCH v6 08/12] netfilter: nf_tables: Wrap netdev notifiers Phil Sutter
` (6 subsequent siblings)
13 siblings, 0 replies; 21+ messages in thread
From: Phil Sutter @ 2025-04-15 15:44 UTC (permalink / raw)
To: Pablo Neira Ayuso; +Cc: netfilter-devel, Florian Westphal, Eric Garver
Hook into new devices if their name matches the hook spec.
Signed-off-by: Phil Sutter <phil@nwl.cc>
---
Changes since v5:
- Prep split into separate patch.
- Merged separate patches for netdev chains and flowtables.
Changes since v3:
- Use list_add_tail_rcu() to avoid breaking readers.
- Use kmemdup() instead of kzalloc() && memcpy() as per Florian.
- Return NOTIFY_BAD upon error instead of printing an error message,
also suggested by Florian.
---
net/netfilter/nf_tables_api.c | 37 +++++++++++++++++++++++++++-----
net/netfilter/nft_chain_filter.c | 32 +++++++++++++++++++++++----
2 files changed, 60 insertions(+), 9 deletions(-)
diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c
index 523dab9b8ef4..f72f6c68c3a3 100644
--- a/net/netfilter/nf_tables_api.c
+++ b/net/netfilter/nf_tables_api.c
@@ -9663,8 +9663,8 @@ struct nf_hook_ops *nft_hook_find_ops_rcu(const struct nft_hook *hook,
}
EXPORT_SYMBOL_GPL(nft_hook_find_ops_rcu);
-static void nft_flowtable_event(unsigned long event, struct net_device *dev,
- struct nft_flowtable *flowtable)
+static int nft_flowtable_event(unsigned long event, struct net_device *dev,
+ struct nft_flowtable *flowtable)
{
struct nf_hook_ops *ops;
struct nft_hook *hook;
@@ -9682,9 +9682,32 @@ static void nft_flowtable_event(unsigned long event, struct net_device *dev,
list_del_rcu(&ops->list);
kfree_rcu(ops, rcu);
break;
+ case NETDEV_REGISTER:
+ if (strcmp(hook->ifname, dev->name))
+ continue;
+
+ ops = kzalloc(sizeof(struct nf_hook_ops),
+ GFP_KERNEL_ACCOUNT);
+ if (!ops)
+ return 1;
+
+ ops->pf = NFPROTO_NETDEV;
+ ops->hooknum = flowtable->hooknum;
+ ops->priority = flowtable->data.priority;
+ ops->priv = &flowtable->data;
+ ops->hook = flowtable->data.type->hook;
+ ops->dev = dev;
+ if (nft_register_flowtable_ops(dev_net(dev),
+ flowtable, ops)) {
+ kfree(ops);
+ return 1;
+ }
+ list_add_tail_rcu(&ops->list, &hook->ops_list);
+ break;
}
break;
}
+ return 0;
}
static int nf_tables_flowtable_event(struct notifier_block *this,
@@ -9696,15 +9719,19 @@ static int nf_tables_flowtable_event(struct notifier_block *this,
struct nft_table *table;
struct net *net;
- if (event != NETDEV_UNREGISTER)
- return 0;
+ if (event != NETDEV_REGISTER &&
+ event != NETDEV_UNREGISTER)
+ return NOTIFY_DONE;
net = dev_net(dev);
nft_net = nft_pernet(net);
mutex_lock(&nft_net->commit_mutex);
list_for_each_entry(table, &nft_net->tables, list) {
list_for_each_entry(flowtable, &table->flowtables, list) {
- nft_flowtable_event(event, dev, flowtable);
+ if (nft_flowtable_event(event, dev, flowtable)) {
+ mutex_unlock(&nft_net->commit_mutex);
+ return NOTIFY_BAD;
+ }
}
}
mutex_unlock(&nft_net->commit_mutex);
diff --git a/net/netfilter/nft_chain_filter.c b/net/netfilter/nft_chain_filter.c
index d22a708e63c8..15a5757a4802 100644
--- a/net/netfilter/nft_chain_filter.c
+++ b/net/netfilter/nft_chain_filter.c
@@ -318,8 +318,8 @@ static const struct nft_chain_type nft_chain_filter_netdev = {
},
};
-static void nft_netdev_event(unsigned long event, struct net_device *dev,
- struct nft_base_chain *basechain)
+static int nft_netdev_event(unsigned long event, struct net_device *dev,
+ struct nft_base_chain *basechain)
{
struct nft_table *table = basechain->chain.table;
struct nf_hook_ops *ops;
@@ -338,8 +338,28 @@ static void nft_netdev_event(unsigned long event, struct net_device *dev,
list_del_rcu(&ops->list);
kfree_rcu(ops, rcu);
break;
+ case NETDEV_REGISTER:
+ if (strcmp(hook->ifname, dev->name))
+ continue;
+
+ ops = kmemdup(&basechain->ops,
+ sizeof(struct nf_hook_ops),
+ GFP_KERNEL_ACCOUNT);
+ if (!ops)
+ return 1;
+
+ ops->dev = dev;
+
+ if (!(table->flags & NFT_TABLE_F_DORMANT) &&
+ nf_register_net_hook(dev_net(dev), ops)) {
+ kfree(ops);
+ return 1;
+ }
+ list_add_tail_rcu(&ops->list, &hook->ops_list);
+ break;
}
}
+ return 0;
}
static int nf_tables_netdev_event(struct notifier_block *this,
@@ -351,7 +371,8 @@ static int nf_tables_netdev_event(struct notifier_block *this,
struct nft_chain *chain;
struct nft_table *table;
- if (event != NETDEV_UNREGISTER)
+ if (event != NETDEV_REGISTER &&
+ event != NETDEV_UNREGISTER)
return NOTIFY_DONE;
nft_net = nft_pernet(dev_net(dev));
@@ -370,7 +391,10 @@ static int nf_tables_netdev_event(struct notifier_block *this,
basechain->ops.hooknum != NF_INET_INGRESS)
continue;
- nft_netdev_event(event, dev, basechain);
+ if (nft_netdev_event(event, dev, basechain)) {
+ mutex_unlock(&nft_net->commit_mutex);
+ return NOTIFY_BAD;
+ }
}
}
mutex_unlock(&nft_net->commit_mutex);
--
2.49.0
^ permalink raw reply related [flat|nested] 21+ messages in thread* [nf-next PATCH v6 08/12] netfilter: nf_tables: Wrap netdev notifiers
2025-04-15 15:44 [nf-next PATCH v6 00/12] Dynamic hook interface binding part 2 Phil Sutter
` (6 preceding siblings ...)
2025-04-15 15:44 ` [nf-next PATCH v6 07/12] netfilter: nf_tables: Respect " Phil Sutter
@ 2025-04-15 15:44 ` Phil Sutter
2025-04-15 15:44 ` [nf-next PATCH v6 09/12] netfilter: nf_tables: Handle NETDEV_CHANGENAME events Phil Sutter
` (5 subsequent siblings)
13 siblings, 0 replies; 21+ messages in thread
From: Phil Sutter @ 2025-04-15 15:44 UTC (permalink / raw)
To: Pablo Neira Ayuso; +Cc: netfilter-devel, Florian Westphal, Eric Garver
Handling NETDEV_CHANGENAME events has to traverse all chains/flowtables
twice, prepare for this. No functional change intended.
Signed-off-by: Phil Sutter <phil@nwl.cc>
---
net/netfilter/nf_tables_api.c | 34 ++++++++++++++++++----------
net/netfilter/nft_chain_filter.c | 38 ++++++++++++++++++++------------
2 files changed, 46 insertions(+), 26 deletions(-)
diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c
index f72f6c68c3a3..ef94b48316a1 100644
--- a/net/netfilter/nf_tables_api.c
+++ b/net/netfilter/nf_tables_api.c
@@ -9710,13 +9710,28 @@ static int nft_flowtable_event(unsigned long event, struct net_device *dev,
return 0;
}
+static int __nf_tables_flowtable_event(unsigned long event,
+ struct net_device *dev)
+{
+ struct nftables_pernet *nft_net = nft_pernet(dev_net(dev));
+ struct nft_flowtable *flowtable;
+ struct nft_table *table;
+
+ list_for_each_entry(table, &nft_net->tables, list) {
+ list_for_each_entry(flowtable, &table->flowtables, list) {
+ if (nft_flowtable_event(event, dev, flowtable))
+ return 1;
+ }
+ }
+ return 0;
+}
+
static int nf_tables_flowtable_event(struct notifier_block *this,
unsigned long event, void *ptr)
{
struct net_device *dev = netdev_notifier_info_to_dev(ptr);
- struct nft_flowtable *flowtable;
struct nftables_pernet *nft_net;
- struct nft_table *table;
+ int ret = NOTIFY_DONE;
struct net *net;
if (event != NETDEV_REGISTER &&
@@ -9726,17 +9741,12 @@ static int nf_tables_flowtable_event(struct notifier_block *this,
net = dev_net(dev);
nft_net = nft_pernet(net);
mutex_lock(&nft_net->commit_mutex);
- list_for_each_entry(table, &nft_net->tables, list) {
- list_for_each_entry(flowtable, &table->flowtables, list) {
- if (nft_flowtable_event(event, dev, flowtable)) {
- mutex_unlock(&nft_net->commit_mutex);
- return NOTIFY_BAD;
- }
- }
- }
- mutex_unlock(&nft_net->commit_mutex);
- return NOTIFY_DONE;
+ if (__nf_tables_flowtable_event(event, dev))
+ ret = NOTIFY_BAD;
+
+ mutex_unlock(&nft_net->commit_mutex);
+ return ret;
}
static struct notifier_block nf_tables_flowtable_notifier = {
diff --git a/net/netfilter/nft_chain_filter.c b/net/netfilter/nft_chain_filter.c
index 15a5757a4802..77b4d363f11e 100644
--- a/net/netfilter/nft_chain_filter.c
+++ b/net/netfilter/nft_chain_filter.c
@@ -362,21 +362,14 @@ static int nft_netdev_event(unsigned long event, struct net_device *dev,
return 0;
}
-static int nf_tables_netdev_event(struct notifier_block *this,
- unsigned long event, void *ptr)
+static int __nf_tables_netdev_event(unsigned long event, struct net_device *dev)
{
- struct net_device *dev = netdev_notifier_info_to_dev(ptr);
struct nft_base_chain *basechain;
struct nftables_pernet *nft_net;
struct nft_chain *chain;
struct nft_table *table;
- if (event != NETDEV_REGISTER &&
- event != NETDEV_UNREGISTER)
- return NOTIFY_DONE;
-
nft_net = nft_pernet(dev_net(dev));
- mutex_lock(&nft_net->commit_mutex);
list_for_each_entry(table, &nft_net->tables, list) {
if (table->family != NFPROTO_NETDEV &&
table->family != NFPROTO_INET)
@@ -391,15 +384,32 @@ static int nf_tables_netdev_event(struct notifier_block *this,
basechain->ops.hooknum != NF_INET_INGRESS)
continue;
- if (nft_netdev_event(event, dev, basechain)) {
- mutex_unlock(&nft_net->commit_mutex);
- return NOTIFY_BAD;
- }
+ if (nft_netdev_event(event, dev, basechain))
+ return 1;
}
}
- mutex_unlock(&nft_net->commit_mutex);
+ return 0;
+}
+
+static int nf_tables_netdev_event(struct notifier_block *this,
+ unsigned long event, void *ptr)
+{
+ struct net_device *dev = netdev_notifier_info_to_dev(ptr);
+ struct nftables_pernet *nft_net;
+ int ret = NOTIFY_DONE;
+
+ if (event != NETDEV_REGISTER &&
+ event != NETDEV_UNREGISTER)
+ return NOTIFY_DONE;
- return NOTIFY_DONE;
+ nft_net = nft_pernet(dev_net(dev));
+ mutex_lock(&nft_net->commit_mutex);
+
+ if (__nf_tables_netdev_event(event, dev))
+ ret = NOTIFY_BAD;
+
+ mutex_unlock(&nft_net->commit_mutex);
+ return ret;
}
static struct notifier_block nf_tables_netdev_notifier = {
--
2.49.0
^ permalink raw reply related [flat|nested] 21+ messages in thread* [nf-next PATCH v6 09/12] netfilter: nf_tables: Handle NETDEV_CHANGENAME events
2025-04-15 15:44 [nf-next PATCH v6 00/12] Dynamic hook interface binding part 2 Phil Sutter
` (7 preceding siblings ...)
2025-04-15 15:44 ` [nf-next PATCH v6 08/12] netfilter: nf_tables: Wrap netdev notifiers Phil Sutter
@ 2025-04-15 15:44 ` Phil Sutter
2025-04-15 15:44 ` [nf-next PATCH v6 10/12] netfilter: nf_tables: Support wildcard netdev hook specs Phil Sutter
` (4 subsequent siblings)
13 siblings, 0 replies; 21+ messages in thread
From: Phil Sutter @ 2025-04-15 15:44 UTC (permalink / raw)
To: Pablo Neira Ayuso; +Cc: netfilter-devel, Florian Westphal, Eric Garver
For the sake of simplicity, treat them like consecutive NETDEV_REGISTER
and NETDEV_UNREGISTER events. If the new name matches a hook spec and
registration fails, escalate the error and keep things as they are.
To avoid unregistering the newly registered hook again during the
following fake NETDEV_UNREGISTER event, leave hooks alone if their
interface spec matches the new name.
Note how this patch also skips for NETDEV_REGISTER if the device is
already registered. This is not yet possible as the new name would have
to match the old one. This will change with wildcard interface specs,
though.
Signed-off-by: Phil Sutter <phil@nwl.cc>
---
Changes since v4:
- Avoid unregistering the new ops along with the old one(s) by accident.
Changes since v3:
- Register first and handle errors to avoid having unregistered the
device but registration fails.
---
net/netfilter/nf_tables_api.c | 33 +++++++++++++++++++++++---------
net/netfilter/nft_chain_filter.c | 33 +++++++++++++++++++++++---------
2 files changed, 48 insertions(+), 18 deletions(-)
diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c
index ef94b48316a1..1e6fde6d3a07 100644
--- a/net/netfilter/nf_tables_api.c
+++ b/net/netfilter/nf_tables_api.c
@@ -9664,16 +9664,20 @@ struct nf_hook_ops *nft_hook_find_ops_rcu(const struct nft_hook *hook,
EXPORT_SYMBOL_GPL(nft_hook_find_ops_rcu);
static int nft_flowtable_event(unsigned long event, struct net_device *dev,
- struct nft_flowtable *flowtable)
+ struct nft_flowtable *flowtable, bool changename)
{
struct nf_hook_ops *ops;
struct nft_hook *hook;
+ bool match;
list_for_each_entry(hook, &flowtable->hook_list, list) {
+ ops = nft_hook_find_ops(hook, dev);
+ match = !strcmp(hook->ifname, dev->name);
+
switch (event) {
case NETDEV_UNREGISTER:
- ops = nft_hook_find_ops(hook, dev);
- if (!ops)
+ /* NOP if not found or new name still matching */
+ if (!ops || (changename && match))
continue;
/* flow_offload_netdev_event() cleans up entries for us. */
@@ -9683,7 +9687,8 @@ static int nft_flowtable_event(unsigned long event, struct net_device *dev,
kfree_rcu(ops, rcu);
break;
case NETDEV_REGISTER:
- if (strcmp(hook->ifname, dev->name))
+ /* NOP if not matching or already registered */
+ if (!match || (changename && ops))
continue;
ops = kzalloc(sizeof(struct nf_hook_ops),
@@ -9711,7 +9716,8 @@ static int nft_flowtable_event(unsigned long event, struct net_device *dev,
}
static int __nf_tables_flowtable_event(unsigned long event,
- struct net_device *dev)
+ struct net_device *dev,
+ bool changename)
{
struct nftables_pernet *nft_net = nft_pernet(dev_net(dev));
struct nft_flowtable *flowtable;
@@ -9719,7 +9725,8 @@ static int __nf_tables_flowtable_event(unsigned long event,
list_for_each_entry(table, &nft_net->tables, list) {
list_for_each_entry(flowtable, &table->flowtables, list) {
- if (nft_flowtable_event(event, dev, flowtable))
+ if (nft_flowtable_event(event, dev,
+ flowtable, changename))
return 1;
}
}
@@ -9735,16 +9742,24 @@ static int nf_tables_flowtable_event(struct notifier_block *this,
struct net *net;
if (event != NETDEV_REGISTER &&
- event != NETDEV_UNREGISTER)
+ event != NETDEV_UNREGISTER &&
+ event != NETDEV_CHANGENAME)
return NOTIFY_DONE;
net = dev_net(dev);
nft_net = nft_pernet(net);
mutex_lock(&nft_net->commit_mutex);
- if (__nf_tables_flowtable_event(event, dev))
+ if (event == NETDEV_CHANGENAME) {
+ if (__nf_tables_flowtable_event(NETDEV_REGISTER, dev, true)) {
+ ret = NOTIFY_BAD;
+ goto out_unlock;
+ }
+ __nf_tables_flowtable_event(NETDEV_UNREGISTER, dev, true);
+ } else if (__nf_tables_flowtable_event(event, dev, false)) {
ret = NOTIFY_BAD;
-
+ }
+out_unlock:
mutex_unlock(&nft_net->commit_mutex);
return ret;
}
diff --git a/net/netfilter/nft_chain_filter.c b/net/netfilter/nft_chain_filter.c
index 77b4d363f11e..89a53b7459a1 100644
--- a/net/netfilter/nft_chain_filter.c
+++ b/net/netfilter/nft_chain_filter.c
@@ -319,17 +319,21 @@ static const struct nft_chain_type nft_chain_filter_netdev = {
};
static int nft_netdev_event(unsigned long event, struct net_device *dev,
- struct nft_base_chain *basechain)
+ struct nft_base_chain *basechain, bool changename)
{
struct nft_table *table = basechain->chain.table;
struct nf_hook_ops *ops;
struct nft_hook *hook;
+ bool match;
list_for_each_entry(hook, &basechain->hook_list, list) {
+ ops = nft_hook_find_ops(hook, dev);
+ match = !strcmp(hook->ifname, dev->name);
+
switch (event) {
case NETDEV_UNREGISTER:
- ops = nft_hook_find_ops(hook, dev);
- if (!ops)
+ /* NOP if not found or new name still matching */
+ if (!ops || (changename && match))
continue;
if (!(table->flags & NFT_TABLE_F_DORMANT))
@@ -339,7 +343,8 @@ static int nft_netdev_event(unsigned long event, struct net_device *dev,
kfree_rcu(ops, rcu);
break;
case NETDEV_REGISTER:
- if (strcmp(hook->ifname, dev->name))
+ /* NOP if not matching or already registered */
+ if (!match || (changename && ops))
continue;
ops = kmemdup(&basechain->ops,
@@ -362,7 +367,9 @@ static int nft_netdev_event(unsigned long event, struct net_device *dev,
return 0;
}
-static int __nf_tables_netdev_event(unsigned long event, struct net_device *dev)
+static int __nf_tables_netdev_event(unsigned long event,
+ struct net_device *dev,
+ bool changename)
{
struct nft_base_chain *basechain;
struct nftables_pernet *nft_net;
@@ -384,7 +391,7 @@ static int __nf_tables_netdev_event(unsigned long event, struct net_device *dev)
basechain->ops.hooknum != NF_INET_INGRESS)
continue;
- if (nft_netdev_event(event, dev, basechain))
+ if (nft_netdev_event(event, dev, basechain, changename))
return 1;
}
}
@@ -399,15 +406,23 @@ static int nf_tables_netdev_event(struct notifier_block *this,
int ret = NOTIFY_DONE;
if (event != NETDEV_REGISTER &&
- event != NETDEV_UNREGISTER)
+ event != NETDEV_UNREGISTER &&
+ event != NETDEV_CHANGENAME)
return NOTIFY_DONE;
nft_net = nft_pernet(dev_net(dev));
mutex_lock(&nft_net->commit_mutex);
- if (__nf_tables_netdev_event(event, dev))
+ if (event == NETDEV_CHANGENAME) {
+ if (__nf_tables_netdev_event(NETDEV_REGISTER, dev, true)) {
+ ret = NOTIFY_BAD;
+ goto out_unlock;
+ }
+ __nf_tables_netdev_event(NETDEV_UNREGISTER, dev, true);
+ } else if (__nf_tables_netdev_event(event, dev, false)) {
ret = NOTIFY_BAD;
-
+ }
+out_unlock:
mutex_unlock(&nft_net->commit_mutex);
return ret;
}
--
2.49.0
^ permalink raw reply related [flat|nested] 21+ messages in thread* [nf-next PATCH v6 10/12] netfilter: nf_tables: Support wildcard netdev hook specs
2025-04-15 15:44 [nf-next PATCH v6 00/12] Dynamic hook interface binding part 2 Phil Sutter
` (8 preceding siblings ...)
2025-04-15 15:44 ` [nf-next PATCH v6 09/12] netfilter: nf_tables: Handle NETDEV_CHANGENAME events Phil Sutter
@ 2025-04-15 15:44 ` Phil Sutter
2025-04-15 15:44 ` [nf-next PATCH v6 11/12] netfilter: nf_tables: Add notications for hook changes Phil Sutter
` (3 subsequent siblings)
13 siblings, 0 replies; 21+ messages in thread
From: Phil Sutter @ 2025-04-15 15:44 UTC (permalink / raw)
To: Pablo Neira Ayuso; +Cc: netfilter-devel, Florian Westphal, Eric Garver
User space may pass non-nul-terminated NFTA_DEVICE_NAME attribute values
to indicate a suffix wildcard.
Expect for multiple devices to match the given prefix in
nft_netdev_hook_alloc() and populate 'ops_list' with them all.
When checking for duplicate hooks, compare the shortest prefix so a
device may never match more than a single hook spec.
Finally respect the stored prefix length when hooking into new devices
from event handlers.
Signed-off-by: Phil Sutter <phil@nwl.cc>
---
net/netfilter/nf_tables_api.c | 33 ++++++++++++++++----------------
net/netfilter/nft_chain_filter.c | 2 +-
2 files changed, 17 insertions(+), 18 deletions(-)
diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c
index 1e6fde6d3a07..04712e5363fb 100644
--- a/net/netfilter/nf_tables_api.c
+++ b/net/netfilter/nf_tables_api.c
@@ -2323,7 +2323,7 @@ static struct nft_hook *nft_netdev_hook_alloc(struct net *net,
err = nla_strscpy(hook->ifname, attr, IFNAMSIZ);
if (err < 0)
- goto err_hook_dev;
+ goto err_ops_alloc;
hook->ifnamelen = nla_len(attr);
@@ -2331,24 +2331,22 @@ static struct nft_hook *nft_netdev_hook_alloc(struct net *net,
* indirectly serializing all the other holders of the commit_mutex with
* the rtnl_mutex.
*/
- dev = __dev_get_by_name(net, hook->ifname);
- if (!dev) {
- err = -ENOENT;
- goto err_hook_dev;
- }
+ for_each_netdev(net, dev) {
+ if (strncmp(dev->name, hook->ifname, hook->ifnamelen))
+ continue;
- ops = kzalloc(sizeof(struct nf_hook_ops), GFP_KERNEL_ACCOUNT);
- if (!ops) {
- err = -ENOMEM;
- goto err_hook_dev;
+ ops = kzalloc(sizeof(struct nf_hook_ops), GFP_KERNEL_ACCOUNT);
+ if (!ops) {
+ err = -ENOMEM;
+ goto err_ops_alloc;
+ }
+ ops->dev = dev;
+ list_add_tail(&ops->list, &hook->ops_list);
}
- ops->dev = dev;
- list_add_tail(&ops->list, &hook->ops_list);
-
return hook;
-err_hook_dev:
- kfree(hook);
+err_ops_alloc:
+ nft_netdev_hook_free(hook);
err_hook_alloc:
return ERR_PTR(err);
}
@@ -2359,7 +2357,8 @@ static struct nft_hook *nft_hook_list_find(struct list_head *hook_list,
struct nft_hook *hook;
list_for_each_entry(hook, hook_list, list) {
- if (!strcmp(hook->ifname, this->ifname))
+ if (!strncmp(hook->ifname, this->ifname,
+ min(hook->ifnamelen, this->ifnamelen)))
return hook;
}
@@ -9672,7 +9671,7 @@ static int nft_flowtable_event(unsigned long event, struct net_device *dev,
list_for_each_entry(hook, &flowtable->hook_list, list) {
ops = nft_hook_find_ops(hook, dev);
- match = !strcmp(hook->ifname, dev->name);
+ match = !strncmp(hook->ifname, dev->name, hook->ifnamelen);
switch (event) {
case NETDEV_UNREGISTER:
diff --git a/net/netfilter/nft_chain_filter.c b/net/netfilter/nft_chain_filter.c
index 89a53b7459a1..2a0c50f7c65d 100644
--- a/net/netfilter/nft_chain_filter.c
+++ b/net/netfilter/nft_chain_filter.c
@@ -328,7 +328,7 @@ static int nft_netdev_event(unsigned long event, struct net_device *dev,
list_for_each_entry(hook, &basechain->hook_list, list) {
ops = nft_hook_find_ops(hook, dev);
- match = !strcmp(hook->ifname, dev->name);
+ match = !strncmp(hook->ifname, dev->name, hook->ifnamelen);
switch (event) {
case NETDEV_UNREGISTER:
--
2.49.0
^ permalink raw reply related [flat|nested] 21+ messages in thread* [nf-next PATCH v6 11/12] netfilter: nf_tables: Add notications for hook changes
2025-04-15 15:44 [nf-next PATCH v6 00/12] Dynamic hook interface binding part 2 Phil Sutter
` (9 preceding siblings ...)
2025-04-15 15:44 ` [nf-next PATCH v6 10/12] netfilter: nf_tables: Support wildcard netdev hook specs Phil Sutter
@ 2025-04-15 15:44 ` Phil Sutter
2025-04-15 15:44 ` [nf-next PATCH v6 12/12] selftests: netfilter: Torture nftables netdev hooks Phil Sutter
` (2 subsequent siblings)
13 siblings, 0 replies; 21+ messages in thread
From: Phil Sutter @ 2025-04-15 15:44 UTC (permalink / raw)
To: Pablo Neira Ayuso; +Cc: netfilter-devel, Florian Westphal, Eric Garver
Notify user space if netdev hooks are updated due to netdev add/remove
events. Send minimal notification messages by introducing
NFT_MSG_NEWDEV/DELDEV message types describing a single device only.
Upon NETDEV_CHANGENAME, the callback has no information about the
interface's old name. To provide a clear message to user space, include
the hook's stored interface name in the notification.
Signed-off-by: Phil Sutter <phil@nwl.cc>
---
Changes since v4:
- Introduce NFTA_DEVICE_SPEC to contain the hook's stored ifname
---
include/net/netfilter/nf_tables.h | 5 ++
include/uapi/linux/netfilter/nf_tables.h | 10 ++++
net/netfilter/nf_tables_api.c | 59 ++++++++++++++++++++++++
net/netfilter/nft_chain_filter.c | 2 +
4 files changed, 76 insertions(+)
diff --git a/include/net/netfilter/nf_tables.h b/include/net/netfilter/nf_tables.h
index 5e49619ae49c..e4d8e451e935 100644
--- a/include/net/netfilter/nf_tables.h
+++ b/include/net/netfilter/nf_tables.h
@@ -1142,6 +1142,11 @@ int nft_set_catchall_validate(const struct nft_ctx *ctx, struct nft_set *set);
int nf_tables_bind_chain(const struct nft_ctx *ctx, struct nft_chain *chain);
void nf_tables_unbind_chain(const struct nft_ctx *ctx, struct nft_chain *chain);
+struct nft_hook;
+void nf_tables_chain_device_notify(const struct nft_chain *chain,
+ const struct nft_hook *hook,
+ const struct net_device *dev, int event);
+
enum nft_chain_types {
NFT_CHAIN_T_DEFAULT = 0,
NFT_CHAIN_T_ROUTE,
diff --git a/include/uapi/linux/netfilter/nf_tables.h b/include/uapi/linux/netfilter/nf_tables.h
index 49c944e78463..80d5f23263c3 100644
--- a/include/uapi/linux/netfilter/nf_tables.h
+++ b/include/uapi/linux/netfilter/nf_tables.h
@@ -142,6 +142,8 @@ enum nf_tables_msg_types {
NFT_MSG_DESTROYOBJ,
NFT_MSG_DESTROYFLOWTABLE,
NFT_MSG_GETSETELEM_RESET,
+ NFT_MSG_NEWDEV,
+ NFT_MSG_DELDEV,
NFT_MSG_MAX,
};
@@ -1780,10 +1782,18 @@ enum nft_synproxy_attributes {
* enum nft_device_attributes - nf_tables device netlink attributes
*
* @NFTA_DEVICE_NAME: name of this device (NLA_STRING)
+ * @NFTA_DEVICE_TABLE: table containing the flowtable or chain hooking into the device (NLA_STRING)
+ * @NFTA_DEVICE_FLOWTABLE: flowtable hooking into the device (NLA_STRING)
+ * @NFTA_DEVICE_CHAIN: chain hooking into the device (NLA_STRING)
+ * @NFTA_DEVICE_SPEC: hook spec matching the device (NLA_STRING)
*/
enum nft_devices_attributes {
NFTA_DEVICE_UNSPEC,
NFTA_DEVICE_NAME,
+ NFTA_DEVICE_TABLE,
+ NFTA_DEVICE_FLOWTABLE,
+ NFTA_DEVICE_CHAIN,
+ NFTA_DEVICE_SPEC,
__NFTA_DEVICE_MAX
};
#define NFTA_DEVICE_MAX (__NFTA_DEVICE_MAX - 1)
diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c
index 04712e5363fb..bd86ddf8915b 100644
--- a/net/netfilter/nf_tables_api.c
+++ b/net/netfilter/nf_tables_api.c
@@ -9662,6 +9662,64 @@ struct nf_hook_ops *nft_hook_find_ops_rcu(const struct nft_hook *hook,
}
EXPORT_SYMBOL_GPL(nft_hook_find_ops_rcu);
+static void
+nf_tables_device_notify(const struct nft_table *table, int attr,
+ const char *name, const struct nft_hook *hook,
+ const struct net_device *dev, int event)
+{
+ struct net *net = dev_net(dev);
+ struct nlmsghdr *nlh;
+ struct sk_buff *skb;
+ u16 flags = 0;
+
+ if (!nfnetlink_has_listeners(net, NFNLGRP_NFTABLES))
+ return;
+
+ skb = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+ if (!skb)
+ goto err;
+
+ event = event == NETDEV_REGISTER ? NFT_MSG_NEWDEV : NFT_MSG_DELDEV;
+ event = nfnl_msg_type(NFNL_SUBSYS_NFTABLES, event);
+ nlh = nfnl_msg_put(skb, 0, 0, event, flags, table->family,
+ NFNETLINK_V0, nft_base_seq(net));
+ if (!nlh)
+ goto err;
+
+ if (nla_put_string(skb, NFTA_DEVICE_TABLE, table->name) ||
+ nla_put_string(skb, attr, name) ||
+ nla_put(skb, NFTA_DEVICE_SPEC, hook->ifnamelen, hook->ifname) ||
+ nla_put_string(skb, NFTA_DEVICE_NAME, dev->name))
+ goto err;
+
+ nlmsg_end(skb, nlh);
+ nfnetlink_send(skb, net, 0, NFNLGRP_NFTABLES,
+ nlmsg_report(nlh), GFP_KERNEL);
+ return;
+err:
+ if (skb)
+ kfree_skb(skb);
+ nfnetlink_set_err(net, 0, NFNLGRP_NFTABLES, -ENOBUFS);
+}
+
+void
+nf_tables_chain_device_notify(const struct nft_chain *chain,
+ const struct nft_hook *hook,
+ const struct net_device *dev, int event)
+{
+ nf_tables_device_notify(chain->table, NFTA_DEVICE_CHAIN,
+ chain->name, hook, dev, event);
+}
+
+static void
+nf_tables_flowtable_device_notify(const struct nft_flowtable *ft,
+ const struct nft_hook *hook,
+ const struct net_device *dev, int event)
+{
+ nf_tables_device_notify(ft->table, NFTA_DEVICE_FLOWTABLE,
+ ft->name, hook, dev, event);
+}
+
static int nft_flowtable_event(unsigned long event, struct net_device *dev,
struct nft_flowtable *flowtable, bool changename)
{
@@ -9709,6 +9767,7 @@ static int nft_flowtable_event(unsigned long event, struct net_device *dev,
list_add_tail_rcu(&ops->list, &hook->ops_list);
break;
}
+ nf_tables_flowtable_device_notify(flowtable, hook, dev, event);
break;
}
return 0;
diff --git a/net/netfilter/nft_chain_filter.c b/net/netfilter/nft_chain_filter.c
index 2a0c50f7c65d..e6d965a56a7a 100644
--- a/net/netfilter/nft_chain_filter.c
+++ b/net/netfilter/nft_chain_filter.c
@@ -363,6 +363,8 @@ static int nft_netdev_event(unsigned long event, struct net_device *dev,
list_add_tail_rcu(&ops->list, &hook->ops_list);
break;
}
+ nf_tables_chain_device_notify(&basechain->chain,
+ hook, dev, event);
}
return 0;
}
--
2.49.0
^ permalink raw reply related [flat|nested] 21+ messages in thread* [nf-next PATCH v6 12/12] selftests: netfilter: Torture nftables netdev hooks
2025-04-15 15:44 [nf-next PATCH v6 00/12] Dynamic hook interface binding part 2 Phil Sutter
` (10 preceding siblings ...)
2025-04-15 15:44 ` [nf-next PATCH v6 11/12] netfilter: nf_tables: Add notications for hook changes Phil Sutter
@ 2025-04-15 15:44 ` Phil Sutter
2025-05-20 10:58 ` [nf-next PATCH v6 00/12] Dynamic hook interface binding part 2 Phil Sutter
2025-05-20 22:28 ` Pablo Neira Ayuso
13 siblings, 0 replies; 21+ messages in thread
From: Phil Sutter @ 2025-04-15 15:44 UTC (permalink / raw)
To: Pablo Neira Ayuso; +Cc: netfilter-devel, Florian Westphal, Eric Garver
Add a ruleset which binds to various interface names via netdev-family
chains and flowtables and massage the notifiers by frequently renaming
interfaces to match these names. While doing so:
- Keep an 'nft monitor' running in background to receive the notifications
- Loop over 'nft list ruleset' to exercise ruleset dump codepath
- Have iperf running so the involved chains/flowtables see traffic
If supported, also test interface wildcard support separately by
creating a flowtable with 'wild*' interface spec and quickly add/remove
matching dummy interfaces.
Signed-off-by: Phil Sutter <phil@nwl.cc>
---
Changes since v5:
- Have iperf report rates in Kbits/s, the value in Mbits/s may contain a
decimal point and therefore break the following zero value check
Changes since v4:
- Limit maximum run-time to 48s.
---
.../testing/selftests/net/netfilter/Makefile | 1 +
.../net/netfilter/nft_interface_stress.sh | 151 ++++++++++++++++++
2 files changed, 152 insertions(+)
create mode 100755 tools/testing/selftests/net/netfilter/nft_interface_stress.sh
diff --git a/tools/testing/selftests/net/netfilter/Makefile b/tools/testing/selftests/net/netfilter/Makefile
index ffe161fac8b5..e7f8985bc903 100644
--- a/tools/testing/selftests/net/netfilter/Makefile
+++ b/tools/testing/selftests/net/netfilter/Makefile
@@ -23,6 +23,7 @@ TEST_PROGS += nft_concat_range.sh
TEST_PROGS += nft_conntrack_helper.sh
TEST_PROGS += nft_fib.sh
TEST_PROGS += nft_flowtable.sh
+TEST_PROGS += nft_interface_stress.sh
TEST_PROGS += nft_meta.sh
TEST_PROGS += nft_nat.sh
TEST_PROGS += nft_nat_zones.sh
diff --git a/tools/testing/selftests/net/netfilter/nft_interface_stress.sh b/tools/testing/selftests/net/netfilter/nft_interface_stress.sh
new file mode 100755
index 000000000000..11d82d11495e
--- /dev/null
+++ b/tools/testing/selftests/net/netfilter/nft_interface_stress.sh
@@ -0,0 +1,151 @@
+#!/bin/bash -e
+#
+# SPDX-License-Identifier: GPL-2.0
+#
+# Torture nftables' netdevice notifier callbacks and related code by frequent
+# renaming of interfaces which netdev-family chains and flowtables hook into.
+
+source lib.sh
+
+checktool "nft --version" "run test without nft tool"
+checktool "iperf3 --version" "run test without iperf3 tool"
+
+# how many seconds to torture the kernel?
+# default to 80% of max run time but don't exceed 48s
+TEST_RUNTIME=$((${kselftest_timeout:-60} * 8 / 10))
+[[ $TEST_RUNTIME -gt 48 ]] && TEST_RUNTIME=48
+
+trap "cleanup_all_ns" EXIT
+
+setup_ns nsc nsr nss
+
+ip -net $nsc link add cr0 type veth peer name rc0 netns $nsr
+ip -net $nsc addr add 10.0.0.1/24 dev cr0
+ip -net $nsc link set cr0 up
+ip -net $nsc route add default via 10.0.0.2
+
+ip -net $nss link add sr0 type veth peer name rs0 netns $nsr
+ip -net $nss addr add 10.1.0.1/24 dev sr0
+ip -net $nss link set sr0 up
+ip -net $nss route add default via 10.1.0.2
+
+ip -net $nsr addr add 10.0.0.2/24 dev rc0
+ip -net $nsr link set rc0 up
+ip -net $nsr addr add 10.1.0.2/24 dev rs0
+ip -net $nsr link set rs0 up
+ip netns exec $nsr sysctl -q net.ipv4.ip_forward=1
+ip netns exec $nsr sysctl -q net.ipv4.conf.all.forwarding=1
+
+{
+ echo "table netdev t {"
+ for ((i = 0; i < 10; i++)); do
+ cat <<-EOF
+ chain chain_rc$i {
+ type filter hook ingress device rc$i priority 0
+ counter
+ }
+ chain chain_rs$i {
+ type filter hook ingress device rs$i priority 0
+ counter
+ }
+ EOF
+ done
+ echo "}"
+ echo "table ip t {"
+ for ((i = 0; i < 10; i++)); do
+ cat <<-EOF
+ flowtable ft_${i} {
+ hook ingress priority 0
+ devices = { rc$i, rs$i }
+ }
+ EOF
+ done
+ echo "chain c {"
+ echo "type filter hook forward priority 0"
+ for ((i = 0; i < 10; i++)); do
+ echo -n "iifname rc$i oifname rs$i "
+ echo "ip protocol tcp counter flow add @ft_${i}"
+ done
+ echo "counter"
+ echo "}"
+ echo "}"
+} | ip netns exec $nsr nft -f - || {
+ echo "SKIP: Could not load nft ruleset"
+ exit $ksft_skip
+}
+
+for ((o=0, n=1; ; o=n, n++, n %= 10)); do
+ ip -net $nsr link set rc$o name rc$n
+ ip -net $nsr link set rs$o name rs$n
+done &
+rename_loop_pid=$!
+
+while true; do ip netns exec $nsr nft list ruleset >/dev/null 2>&1; done &
+nft_list_pid=$!
+
+ip netns exec $nsr nft monitor >/dev/null &
+nft_monitor_pid=$!
+
+ip netns exec $nss iperf3 --server --daemon -1
+summary_expr='s,^\[SUM\] .* \([0-9\.]\+\) Kbits/sec .* receiver,\1,p'
+rate=$(ip netns exec $nsc iperf3 \
+ --format k -c 10.1.0.1 --time $TEST_RUNTIME \
+ --length 56 --parallel 10 -i 0 | sed -n "$summary_expr")
+
+kill $nft_list_pid
+kill $nft_monitor_pid
+kill $rename_loop_pid
+wait
+
+ip netns exec $nsr nft -f - <<EOF
+table ip t {
+ flowtable ft_wild {
+ hook ingress priority 0
+ devices = { wild* }
+ }
+}
+EOF
+if [[ $? -ne 0 ]]; then
+ echo "SKIP wildcard tests: not supported by host's nft?"
+else
+ for ((i = 0; i < 100; i++)); do
+ ip -net $nsr link add wild$i type dummy &
+ done
+ wait
+ for ((i = 80; i < 100; i++)); do
+ ip -net $nsr link del wild$i &
+ done
+ for ((i = 0; i < 80; i++)); do
+ ip -net $nsr link del wild$i &
+ done
+ wait
+ for ((i = 0; i < 100; i += 10)); do
+ (
+ for ((j = 0; j < 10; j++)); do
+ ip -net $nsr link add wild$((i + j)) type dummy
+ done
+ for ((j = 0; j < 10; j++)); do
+ ip -net $nsr link del wild$((i + j))
+ done
+ ) &
+ done
+ wait
+fi
+
+[[ $(</proc/sys/kernel/tainted) -eq 0 ]] || {
+ echo "FAIL: Kernel is tainted!"
+ exit $ksft_fail
+}
+
+[[ $rate -gt 0 ]] || {
+ echo "FAIL: Zero throughput in iperf3"
+ exit $ksft_fail
+}
+
+[[ -f /sys/kernel/debug/kmemleak && \
+ -n $(</sys/kernel/debug/kmemleak) ]] && {
+ echo "FAIL: non-empty kmemleak report"
+ exit $ksft_fail
+}
+
+exit $ksft_pass
--
2.49.0
^ permalink raw reply related [flat|nested] 21+ messages in thread* Re: [nf-next PATCH v6 00/12] Dynamic hook interface binding part 2
2025-04-15 15:44 [nf-next PATCH v6 00/12] Dynamic hook interface binding part 2 Phil Sutter
` (11 preceding siblings ...)
2025-04-15 15:44 ` [nf-next PATCH v6 12/12] selftests: netfilter: Torture nftables netdev hooks Phil Sutter
@ 2025-05-20 10:58 ` Phil Sutter
2025-05-20 11:03 ` Pablo Neira Ayuso
2025-05-20 22:28 ` Pablo Neira Ayuso
13 siblings, 1 reply; 21+ messages in thread
From: Phil Sutter @ 2025-05-20 10:58 UTC (permalink / raw)
To: Pablo Neira Ayuso; +Cc: netfilter-devel, Florian Westphal, Eric Garver
Bump!
Anything I can do to help push this forward? The series I submitted to
add support for this to libnftnl and nftables should still apply as-is.
Anything else missing on my end? Or should I try to break this down into
smaller patches/chunks?
Thanks, Phil
On Tue, Apr 15, 2025 at 05:44:28PM +0200, Phil Sutter wrote:
> Changes since v5:
> - First part split into separate series (applied and present in Linus'
> git already).
> - Add nft_hook_find_ops_rcu() in patch 2 already to reduce size of patch
> 5.
> - New patch 4 to reduce size of patch 5.
> - New patch 6 preparing for patch 7 which in turn combines identical
> changes to both flowtables and netdev chains.
>
> Patches 1-5 prepare for and implement nf_hook_ops lists in nft_hook
> objects. This is crucial for wildcard interface specs and convenient
> with dynamic netdev hook registration upon NETDEV_REGISTER events.
>
> Patches 6-9 leverage the new infrastructure to correctly handle
> NETDEV_REGISTER and NETDEV_CHANGENAME events.
>
> Patch 10 prepares the code for non-NUL-terminated interface names passed
> by user space which resemble prefixes to match on. As a side-effect,
> hook allocation code becomes tolerant to non-matching interface specs.
>
> The final two patches implement netlink notifications for netdev
> add/remove events and add a kselftest.
>
> Phil Sutter (12):
> netfilter: nf_tables: Introduce functions freeing nft_hook objects
> netfilter: nf_tables: Introduce nft_hook_find_ops{,_rcu}()
> netfilter: nf_tables: Introduce nft_register_flowtable_ops()
> netfilter: nf_tables: Pass nf_hook_ops to
> nft_unregister_flowtable_hook()
> netfilter: nf_tables: Have a list of nf_hook_ops in nft_hook
> netfilter: nf_tables: Prepare for handling NETDEV_REGISTER events
> netfilter: nf_tables: Respect NETDEV_REGISTER events
> netfilter: nf_tables: Wrap netdev notifiers
> netfilter: nf_tables: Handle NETDEV_CHANGENAME events
> netfilter: nf_tables: Support wildcard netdev hook specs
> netfilter: nf_tables: Add notications for hook changes
> selftests: netfilter: Torture nftables netdev hooks
>
> include/linux/netfilter.h | 3 +
> include/net/netfilter/nf_tables.h | 12 +-
> include/uapi/linux/netfilter/nf_tables.h | 10 +
> net/netfilter/nf_tables_api.c | 394 ++++++++++++++----
> net/netfilter/nf_tables_offload.c | 51 ++-
> net/netfilter/nft_chain_filter.c | 95 ++++-
> net/netfilter/nft_flow_offload.c | 2 +-
> .../testing/selftests/net/netfilter/Makefile | 1 +
> .../net/netfilter/nft_interface_stress.sh | 151 +++++++
> 9 files changed, 587 insertions(+), 132 deletions(-)
> create mode 100755 tools/testing/selftests/net/netfilter/nft_interface_stress.sh
>
> --
> 2.49.0
>
>
>
^ permalink raw reply [flat|nested] 21+ messages in thread* Re: [nf-next PATCH v6 00/12] Dynamic hook interface binding part 2
2025-05-20 10:58 ` [nf-next PATCH v6 00/12] Dynamic hook interface binding part 2 Phil Sutter
@ 2025-05-20 11:03 ` Pablo Neira Ayuso
2025-05-20 11:08 ` Phil Sutter
0 siblings, 1 reply; 21+ messages in thread
From: Pablo Neira Ayuso @ 2025-05-20 11:03 UTC (permalink / raw)
To: Phil Sutter, netfilter-devel, Florian Westphal, Eric Garver
On Tue, May 20, 2025 at 12:58:40PM +0200, Phil Sutter wrote:
> Bump!
>
> Anything I can do to help push this forward? The series I submitted to
> add support for this to libnftnl and nftables should still apply as-is.
> Anything else missing on my end? Or should I try to break this down into
> smaller patches/chunks?
I was exactly now looking into integrating this into nf-next, sorry
for the slow turn around.
> Thanks, Phil
>
> On Tue, Apr 15, 2025 at 05:44:28PM +0200, Phil Sutter wrote:
> > Changes since v5:
> > - First part split into separate series (applied and present in Linus'
> > git already).
> > - Add nft_hook_find_ops_rcu() in patch 2 already to reduce size of patch
> > 5.
> > - New patch 4 to reduce size of patch 5.
> > - New patch 6 preparing for patch 7 which in turn combines identical
> > changes to both flowtables and netdev chains.
> >
> > Patches 1-5 prepare for and implement nf_hook_ops lists in nft_hook
> > objects. This is crucial for wildcard interface specs and convenient
> > with dynamic netdev hook registration upon NETDEV_REGISTER events.
> >
> > Patches 6-9 leverage the new infrastructure to correctly handle
> > NETDEV_REGISTER and NETDEV_CHANGENAME events.
> >
> > Patch 10 prepares the code for non-NUL-terminated interface names passed
> > by user space which resemble prefixes to match on. As a side-effect,
> > hook allocation code becomes tolerant to non-matching interface specs.
> >
> > The final two patches implement netlink notifications for netdev
> > add/remove events and add a kselftest.
> >
> > Phil Sutter (12):
> > netfilter: nf_tables: Introduce functions freeing nft_hook objects
> > netfilter: nf_tables: Introduce nft_hook_find_ops{,_rcu}()
> > netfilter: nf_tables: Introduce nft_register_flowtable_ops()
> > netfilter: nf_tables: Pass nf_hook_ops to
> > nft_unregister_flowtable_hook()
> > netfilter: nf_tables: Have a list of nf_hook_ops in nft_hook
> > netfilter: nf_tables: Prepare for handling NETDEV_REGISTER events
> > netfilter: nf_tables: Respect NETDEV_REGISTER events
> > netfilter: nf_tables: Wrap netdev notifiers
> > netfilter: nf_tables: Handle NETDEV_CHANGENAME events
> > netfilter: nf_tables: Support wildcard netdev hook specs
> > netfilter: nf_tables: Add notications for hook changes
> > selftests: netfilter: Torture nftables netdev hooks
> >
> > include/linux/netfilter.h | 3 +
> > include/net/netfilter/nf_tables.h | 12 +-
> > include/uapi/linux/netfilter/nf_tables.h | 10 +
> > net/netfilter/nf_tables_api.c | 394 ++++++++++++++----
> > net/netfilter/nf_tables_offload.c | 51 ++-
> > net/netfilter/nft_chain_filter.c | 95 ++++-
> > net/netfilter/nft_flow_offload.c | 2 +-
> > .../testing/selftests/net/netfilter/Makefile | 1 +
> > .../net/netfilter/nft_interface_stress.sh | 151 +++++++
> > 9 files changed, 587 insertions(+), 132 deletions(-)
> > create mode 100755 tools/testing/selftests/net/netfilter/nft_interface_stress.sh
> >
> > --
> > 2.49.0
> >
> >
> >
^ permalink raw reply [flat|nested] 21+ messages in thread* Re: [nf-next PATCH v6 00/12] Dynamic hook interface binding part 2
2025-05-20 11:03 ` Pablo Neira Ayuso
@ 2025-05-20 11:08 ` Phil Sutter
0 siblings, 0 replies; 21+ messages in thread
From: Phil Sutter @ 2025-05-20 11:08 UTC (permalink / raw)
To: Pablo Neira Ayuso; +Cc: netfilter-devel, Florian Westphal, Eric Garver
On Tue, May 20, 2025 at 01:03:32PM +0200, Pablo Neira Ayuso wrote:
> On Tue, May 20, 2025 at 12:58:40PM +0200, Phil Sutter wrote:
> > Bump!
> >
> > Anything I can do to help push this forward? The series I submitted to
> > add support for this to libnftnl and nftables should still apply as-is.
> > Anything else missing on my end? Or should I try to break this down into
> > smaller patches/chunks?
>
> I was exactly now looking into integrating this into nf-next, sorry
> for the slow turn around.
Nice! I obviously start to sense when someone reviews a patch of mine.
:D
Thanks, Phil
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [nf-next PATCH v6 00/12] Dynamic hook interface binding part 2
2025-04-15 15:44 [nf-next PATCH v6 00/12] Dynamic hook interface binding part 2 Phil Sutter
` (12 preceding siblings ...)
2025-05-20 10:58 ` [nf-next PATCH v6 00/12] Dynamic hook interface binding part 2 Phil Sutter
@ 2025-05-20 22:28 ` Pablo Neira Ayuso
2025-05-21 15:32 ` Phil Sutter
13 siblings, 1 reply; 21+ messages in thread
From: Pablo Neira Ayuso @ 2025-05-20 22:28 UTC (permalink / raw)
To: Phil Sutter; +Cc: netfilter-devel, Florian Westphal, Eric Garver
Hi Phil,
This looks very good, I still have a few comments, related to three
patches:
== netfilter: nf_tables: Have a list of nf_hook_ops in nft_hook
1) There's a possible inconsistent use of list_for_each_entry{_safe}
while calling nf_unregister_net_hook().
static void nft_netdev_unregister_hooks(struct net *net,
struct list_head *hook_list,
bool release_netdev)
{
+ struct nf_hook_ops *ops, *nextops;
struct nft_hook *hook, *next;
list_for_each_entry_safe(hook, next, hook_list, list) {
- nf_unregister_net_hook(net, &hook->ops);
+ list_for_each_entry_safe(ops, nextops, &hook->ops_list, list) <--- HERE
+ nf_unregister_net_hook(net, ops);
[...]
@@ -2923,8 +2962,10 @@ static int nf_tables_updchain(struct nft_ctx *ctx, u8 genmask, u8 policy,
err_hooks:
if (nla[NFTA_CHAIN_HOOK]) {
list_for_each_entry_safe(h, next, &hook.list, list) {
- if (unregister)
- nf_unregister_net_hook(ctx->net, &h->ops);
+ if (unregister) {
+ list_for_each_entry(ops, &h->ops_list, list) <--- HERE
+ nf_unregister_net_hook(ctx->net, ops);
Which one should be adjusted? I think _safe can be removed?
Maybe add nf_unregister_net_hook_list() helper? It helps to avoid
future similar issues.
2) I wonder if nft_hook_find_ops() will need a hashtable sooner or
later. With the wildcard, the number of devices could be significantly
large in this list lookup.
@@ -9611,9 +9666,12 @@ static int nf_tables_fill_gen_info(struct sk_buff *skb, struct net *net,
struct nf_hook_ops *nft_hook_find_ops(const struct nft_hook *hook,
const struct net_device *dev)
{
- if (hook->ops.dev == dev)
- return (struct nf_hook_ops *)&hook->ops;
+ struct nf_hook_ops *ops;
+ list_for_each_entry(ops, &hook->ops_list, list) {
+ if (ops->dev == dev)
+ return ops;
+ }
return NULL;
}
3) Maybe move struct rcu_head at the end of struct nf_hook_ops?
struct nf_hook_ops {
+ struct list_head list;
+ struct rcu_head rcu; <--- move it at the end of this struct?
This is a control plane object, but still it is common to place
this at the end. But not a deal breaker.
4) nft_netdev_event() is missing a break; I think it is an overlook?
diff --git a/net/netfilter/nft_chain_filter.c b/net/netfilter/nft_chain_filter.c
index 783e4b5ef3e0..bac5aa8970a4 100644
--- a/net/netfilter/nft_chain_filter.c
+++ b/net/netfilter/nft_chain_filter.c
@@ -332,9 +332,8 @@ static void nft_netdev_event(unsigned long event, struct net_device *dev,
if (!(basechain->chain.table->flags & NFT_TABLE_F_DORMANT))
nf_unregister_net_hook(dev_net(dev), ops);
- list_del_rcu(&hook->list);
- kfree_rcu(hook, rcu);
- break; <------------------------- this is gone!
+ list_del_rcu(&ops->list);
+ kfree_rcu(ops, rcu);
}
}
but I can still see break; in the flowtable event handler.
So nft_netdev_event() shows no break;
But nft_flowtable_event() still has a break;
== Support wildcard netdev hook specs
Nitpick: the err_ops_alloc: tag takes me to nft_netdev_hook_free(hook);
maybe better rename it to err_hook_free: ? Because currently
err_ops_alloc takes me to nft_netdev_hook_free(hook);
@@ -2323,7 +2323,7 @@ static struct nft_hook *nft_netdev_hook_alloc(struct net *net,
err = nla_strscpy(hook->ifname, attr, IFNAMSIZ);
if (err < 0)
- goto err_hook_dev;
+ goto err_ops_alloc;
but takes you to free the hook:
-err_hook_dev:
- kfree(hook);
+err_ops_alloc:
+ nft_netdev_hook_free(hook);
== netfilter: nf_tables: Add "notications" <-- typo: "notifications"
I suggest you add a new NFNLGRP_NFT_DEV group for these notifications,
so NFNLGRP_NFTABLES is only used for control plane updates via
nfnetlink API. In this case, these events are triggered by rtnetlink
when a new device is registered and it matches the existing an
existing device if I understood the rationale.
Thanks.
^ permalink raw reply related [flat|nested] 21+ messages in thread* Re: [nf-next PATCH v6 00/12] Dynamic hook interface binding part 2
2025-05-20 22:28 ` Pablo Neira Ayuso
@ 2025-05-21 15:32 ` Phil Sutter
2025-05-21 15:49 ` Phil Sutter
2025-05-21 15:51 ` Pablo Neira Ayuso
0 siblings, 2 replies; 21+ messages in thread
From: Phil Sutter @ 2025-05-21 15:32 UTC (permalink / raw)
To: Pablo Neira Ayuso; +Cc: netfilter-devel, Florian Westphal, Eric Garver
Hi Pablo,
On Wed, May 21, 2025 at 12:28:01AM +0200, Pablo Neira Ayuso wrote:
> This looks very good, I still have a few comments, related to three
> patches:
>
> == netfilter: nf_tables: Have a list of nf_hook_ops in nft_hook
>
> 1) There's a possible inconsistent use of list_for_each_entry{_safe}
> while calling nf_unregister_net_hook().
>
> static void nft_netdev_unregister_hooks(struct net *net,
> struct list_head *hook_list,
> bool release_netdev)
> {
> + struct nf_hook_ops *ops, *nextops;
> struct nft_hook *hook, *next;
>
> list_for_each_entry_safe(hook, next, hook_list, list) {
> - nf_unregister_net_hook(net, &hook->ops);
> + list_for_each_entry_safe(ops, nextops, &hook->ops_list, list) <--- HERE
> + nf_unregister_net_hook(net, ops);
>
> [...]
>
> @@ -2923,8 +2962,10 @@ static int nf_tables_updchain(struct nft_ctx *ctx, u8 genmask, u8 policy,
> err_hooks:
> if (nla[NFTA_CHAIN_HOOK]) {
> list_for_each_entry_safe(h, next, &hook.list, list) {
> - if (unregister)
> - nf_unregister_net_hook(ctx->net, &h->ops);
> + if (unregister) {
> + list_for_each_entry(ops, &h->ops_list, list) <--- HERE
> + nf_unregister_net_hook(ctx->net, ops);
>
> Which one should be adjusted? I think _safe can be removed?
Oh, right. Yes, the _save is pointless since no items are removed inside
the loop.
> Maybe add nf_unregister_net_hook_list() helper? It helps to avoid
> future similar issues.
ACK, will do!
> 2) I wonder if nft_hook_find_ops() will need a hashtable sooner or
> later. With the wildcard, the number of devices could be significantly
> large in this list lookup.
Maybe, yes. Is it useful to have a single flowtable for all virtual
functions (e.g.) on a hypervisor? Or would one rather have distinct
flowtables for each VM/container?
> @@ -9611,9 +9666,12 @@ static int nf_tables_fill_gen_info(struct sk_buff *skb, struct net *net,
> struct nf_hook_ops *nft_hook_find_ops(const struct nft_hook *hook,
> const struct net_device *dev)
> {
> - if (hook->ops.dev == dev)
> - return (struct nf_hook_ops *)&hook->ops;
> + struct nf_hook_ops *ops;
>
> + list_for_each_entry(ops, &hook->ops_list, list) {
> + if (ops->dev == dev)
> + return ops;
> + }
> return NULL;
> }
Callers are:
- nft_{flowtable,netdev}_event(): Interface is added or removed
- nft_flow_offload_eval(): New flow being offloaded
- nft_offload_netdev_event(): Interface is removed
All these are "slow path" at least. I could try building a test case to
see how performance scales, but since we hit the function just once for
each new connection, I guess it's hard to get significant data out of
it.
> 3) Maybe move struct rcu_head at the end of struct nf_hook_ops?
>
> struct nf_hook_ops {
> + struct list_head list;
> + struct rcu_head rcu; <--- move it at the end of this struct?
>
> This is a control plane object, but still it is common to place
> this at the end. But not a deal breaker.
I tried to copy struct nft_hook in that regard which has the rcu_head
right after the list_heads. AIUI, both these types are "handles" to
carry the object around, having the actual payload follow them feels
more natural to me. If you prefer the "append new fields" approach, fine
with me though!
> 4) nft_netdev_event() is missing a break; I think it is an overlook?
>
> diff --git a/net/netfilter/nft_chain_filter.c b/net/netfilter/nft_chain_filter.c
> index 783e4b5ef3e0..bac5aa8970a4 100644
> --- a/net/netfilter/nft_chain_filter.c
> +++ b/net/netfilter/nft_chain_filter.c
> @@ -332,9 +332,8 @@ static void nft_netdev_event(unsigned long event, struct net_device *dev,
> if (!(basechain->chain.table->flags & NFT_TABLE_F_DORMANT))
> nf_unregister_net_hook(dev_net(dev), ops);
>
> - list_del_rcu(&hook->list);
> - kfree_rcu(hook, rcu);
> - break; <------------------------- this is gone!
> + list_del_rcu(&ops->list);
> + kfree_rcu(ops, rcu);
> }
> }
>
> but I can still see break; in the flowtable event handler.
>
> So nft_netdev_event() shows no break;
> But nft_flowtable_event() still has a break;
I recall being undecided about this. A relevant difference between
netdev chains and flowtables is that a device may have multiple netdev
chain hooks but only a single flowtable one[1]. This might have confused
me, because the break above effectively means "stop searching for a
matching hook in this chain if one was found for the given interface
already". Since neither chains nor flowtables allow for overlapping
hooks (e.g. { eth1*, eth11 }), a given device matches at most a single
hook. I'll adjust the series to keep this break, then. Thanks!
> == Support wildcard netdev hook specs
>
> Nitpick: the err_ops_alloc: tag takes me to nft_netdev_hook_free(hook);
> maybe better rename it to err_hook_free: ? Because currently
> err_ops_alloc takes me to nft_netdev_hook_free(hook);
>
> @@ -2323,7 +2323,7 @@ static struct nft_hook *nft_netdev_hook_alloc(struct net *net,
>
> err = nla_strscpy(hook->ifname, attr, IFNAMSIZ);
> if (err < 0)
> - goto err_hook_dev;
> + goto err_ops_alloc;
>
> but takes you to free the hook:
>
> -err_hook_dev:
> - kfree(hook);
> +err_ops_alloc:
> + nft_netdev_hook_free(hook);
Oh yes, this is a mess. Also, the err_hook_alloc tag is pointless as it
leads to an immediate return.
> == netfilter: nf_tables: Add "notications" <-- typo: "notifications"
>
> I suggest you add a new NFNLGRP_NFT_DEV group for these notifications,
> so NFNLGRP_NFTABLES is only used for control plane updates via
> nfnetlink API. In this case, these events are triggered by rtnetlink
> when a new device is registered and it matches the existing an
> existing device if I understood the rationale.
Yes, MSG_NEWDEV and MSG_DELDEV are triggered if a new device matches a
hook or if a hooked device is removed (or renamed, so the hook won't
match anymore).
Having a distinct NFNLGRP for them requires a new 'nft monitor' mode,
right? So we can't have a single monitor process for ruleset changes and
these device events. Should not be a problem, though.
Thanks, Phil
[1] nft_register_flowtable_net_hooks() searches for a matching hook in
other flowtables of the same table.
^ permalink raw reply [flat|nested] 21+ messages in thread* Re: [nf-next PATCH v6 00/12] Dynamic hook interface binding part 2
2025-05-21 15:32 ` Phil Sutter
@ 2025-05-21 15:49 ` Phil Sutter
2025-05-21 15:51 ` Pablo Neira Ayuso
1 sibling, 0 replies; 21+ messages in thread
From: Phil Sutter @ 2025-05-21 15:49 UTC (permalink / raw)
To: Pablo Neira Ayuso, netfilter-devel, Florian Westphal, Eric Garver
On Wed, May 21, 2025 at 05:32:57PM +0200, Phil Sutter wrote:
> On Wed, May 21, 2025 at 12:28:01AM +0200, Pablo Neira Ayuso wrote:
[...]
> > Maybe add nf_unregister_net_hook_list() helper? It helps to avoid
> > future similar issues.
>
> ACK, will do!
Scratch that, I was too quick and didn't check: There are exactly two
spots which run 'list_for_each_entry(...) { nf_register_net_hook() }'
without any extras. I'll just fix the one spot instead.
Cheers, Phil
^ permalink raw reply [flat|nested] 21+ messages in thread* Re: [nf-next PATCH v6 00/12] Dynamic hook interface binding part 2
2025-05-21 15:32 ` Phil Sutter
2025-05-21 15:49 ` Phil Sutter
@ 2025-05-21 15:51 ` Pablo Neira Ayuso
2025-05-21 16:46 ` Phil Sutter
1 sibling, 1 reply; 21+ messages in thread
From: Pablo Neira Ayuso @ 2025-05-21 15:51 UTC (permalink / raw)
To: Phil Sutter, netfilter-devel, Florian Westphal, Eric Garver
Hi Phil,
On Wed, May 21, 2025 at 05:32:57PM +0200, Phil Sutter wrote:
> Hi Pablo,
>
> On Wed, May 21, 2025 at 12:28:01AM +0200, Pablo Neira Ayuso wrote:
[...]
> > 2) I wonder if nft_hook_find_ops() will need a hashtable sooner or
> > later. With the wildcard, the number of devices could be significantly
> > large in this list lookup.
>
> Maybe, yes. Is it useful to have a single flowtable for all virtual
> functions (e.g.) on a hypervisor? Or would one rather have distinct
> flowtables for each VM/container?
[...]
> Callers are:
>
> - nft_{flowtable,netdev}_event(): Interface is added or removed
> - nft_flow_offload_eval(): New flow being offloaded
> - nft_offload_netdev_event(): Interface is removed
>
> All these are "slow path" at least. I could try building a test case to
> see how performance scales, but since we hit the function just once for
> each new connection, I guess it's hard to get significant data out of
> it.
This can be added later, not a deal breaker.
This is event path which might slow down adding new entries via
rtnetlink maybe, but I would need to have a closer look.
[...]
> > == netfilter: nf_tables: Add "notications" <-- typo: "notifications"
> >
> > I suggest you add a new NFNLGRP_NFT_DEV group for these notifications,
> > so NFNLGRP_NFTABLES is only used for control plane updates via
> > nfnetlink API. In this case, these events are triggered by rtnetlink
> > when a new device is registered and it matches the existing an
> > existing device if I understood the rationale.
>
> Yes, MSG_NEWDEV and MSG_DELDEV are triggered if a new device matches a
> hook or if a hooked device is removed (or renamed, so the hook won't
> match anymore).
>
> Having a distinct NFNLGRP for them requires a new 'nft monitor' mode,
> right? So we can't have a single monitor process for ruleset changes and
> these device events. Should not be a problem, though.
Having a different group allows to filter out events you might not
care about, it is a simple netlink event filtering facility.
I think this feature is for debugging purpose only, correct? So a
separated group should be fine. IIUC, this event does not modify the
ruleset, it only tells us a hook for a matching device is being
registered.
Thanks.
^ permalink raw reply [flat|nested] 21+ messages in thread* Re: [nf-next PATCH v6 00/12] Dynamic hook interface binding part 2
2025-05-21 15:51 ` Pablo Neira Ayuso
@ 2025-05-21 16:46 ` Phil Sutter
0 siblings, 0 replies; 21+ messages in thread
From: Phil Sutter @ 2025-05-21 16:46 UTC (permalink / raw)
To: Pablo Neira Ayuso; +Cc: netfilter-devel, Florian Westphal, Eric Garver
On Wed, May 21, 2025 at 05:51:50PM +0200, Pablo Neira Ayuso wrote:
> Hi Phil,
>
> On Wed, May 21, 2025 at 05:32:57PM +0200, Phil Sutter wrote:
> > Hi Pablo,
> >
> > On Wed, May 21, 2025 at 12:28:01AM +0200, Pablo Neira Ayuso wrote:
> [...]
> > > 2) I wonder if nft_hook_find_ops() will need a hashtable sooner or
> > > later. With the wildcard, the number of devices could be significantly
> > > large in this list lookup.
> >
> > Maybe, yes. Is it useful to have a single flowtable for all virtual
> > functions (e.g.) on a hypervisor? Or would one rather have distinct
> > flowtables for each VM/container?
> [...]
> > Callers are:
> >
> > - nft_{flowtable,netdev}_event(): Interface is added or removed
> > - nft_flow_offload_eval(): New flow being offloaded
> > - nft_offload_netdev_event(): Interface is removed
> >
> > All these are "slow path" at least. I could try building a test case to
> > see how performance scales, but since we hit the function just once for
> > each new connection, I guess it's hard to get significant data out of
> > it.
>
> This can be added later, not a deal breaker.
ACK.
> This is event path which might slow down adding new entries via
> rtnetlink maybe, but I would need to have a closer look.
We'd optimize for flowtables with many devices. The same system could
also have many flowtables with few devices each, then the hash table
adds to the overhead. A global table for all flowtables could serve
both. We could also have the same device in multiple chains, so each
hash table entry needs a list of ops pointers?
> [...]
> > > == netfilter: nf_tables: Add "notications" <-- typo: "notifications"
> > >
> > > I suggest you add a new NFNLGRP_NFT_DEV group for these notifications,
> > > so NFNLGRP_NFTABLES is only used for control plane updates via
> > > nfnetlink API. In this case, these events are triggered by rtnetlink
> > > when a new device is registered and it matches the existing an
> > > existing device if I understood the rationale.
> >
> > Yes, MSG_NEWDEV and MSG_DELDEV are triggered if a new device matches a
> > hook or if a hooked device is removed (or renamed, so the hook won't
> > match anymore).
> >
> > Having a distinct NFNLGRP for them requires a new 'nft monitor' mode,
> > right? So we can't have a single monitor process for ruleset changes and
> > these device events. Should not be a problem, though.
>
> Having a different group allows to filter out events you might not
> care about, it is a simple netlink event filtering facility.
>
> I think this feature is for debugging purpose only, correct? So a
> separated group should be fine. IIUC, this event does not modify the
> ruleset, it only tells us a hook for a matching device is being
> registered.
Yes, you're right. Strictly speaking, these events are different from
NFNLGRP_NFTABLES as they don't indicate a ruleset change. Their actual
purpose is to satisfy the requirement of hook reg/unreg being visible to
user space. :)
nft monitor needs some work, though: Right now it's in trace or !trace
mode depending on whether NFT_MSG_TRACE is present in monitor_flags. Now
there will be a third modus operandi. OK, we could hide that internally
and enable the new group automatically if not tracing, then add a
'hooks' keyword so users may 'nft monitor (new|destroy) hooks'.
Cheers, Phil
^ permalink raw reply [flat|nested] 21+ messages in thread