netfilter: nf_tables: Have a list of nf_hook_ops in nft_hook

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>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
This commit is contained in:
Phil Sutter
2025-05-21 22:44:26 +02:00
committed by Pablo Neira Ayuso
parent 91a089d056
commit 73319a8ee1
5 changed files with 136 additions and 62 deletions

View File

@@ -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;

View File

@@ -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;

View File

@@ -300,37 +300,60 @@ 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);
list_for_each_entry(ops, &hook->ops_list, list) {
err = nf_register_net_hook(net, ops);
if (err < 0)
goto err_register;
j++;
}
}
return 0;
err_register:
list_for_each_entry(hook, hook_list, list) {
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,
@@ -338,9 +361,11 @@ static void nft_netdev_unregister_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) {
nf_unregister_net_hook(net, &hook->ops);
list_for_each_entry(ops, &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 @@ err_trans:
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);
}
@@ -8795,6 +8836,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;
@@ -8849,11 +8891,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;
@@ -8910,9 +8954,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);
@@ -8954,6 +9000,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) {
@@ -8967,21 +9014,25 @@ static int nft_register_flowtable_net_hooks(struct net *net,
}
}
err = nft_register_flowtable_ops(net, flowtable, &hook->ops);
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++;
}
}
return 0;
err_unregister_net_hooks:
list_for_each_entry_safe(hook, next, hook_list, list) {
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);
}
@@ -9006,6 +9057,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;
@@ -9063,8 +9115,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);
}
@@ -9611,9 +9666,12 @@ nla_put_failure:
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);
@@ -9621,7 +9679,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);
@@ -9638,8 +9702,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;
}
}

View File

@@ -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,14 +228,17 @@ 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)
list_for_each_entry(ops, &hook->ops_list, list) {
if (ops->pf != NFPROTO_NETDEV ||
ops->hooknum != NF_NETDEV_INGRESS)
return false;
dev = hook->ops.dev;
if (!dev->netdev_ops->ndo_setup_tc && !flow_indr_dev_exists())
dev = ops->dev;
if (!dev->netdev_ops->ndo_setup_tc &&
!flow_indr_dev_exists())
return false;
}
}
return true;
}
@@ -455,16 +459,16 @@ 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)
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);
err = nft_chain_offload_cmd(basechain, ops->dev, cmd);
if (err < 0 && cmd == FLOW_BLOCK_BIND) {
if (!this_dev)
goto err_flow_block;
@@ -473,16 +477,19 @@ static int nft_flow_block_chain(struct nft_base_chain *basechain,
}
i++;
}
}
return 0;
err_flow_block:
list_for_each_entry(hook, &basechain->hook_list, list) {
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;
}

View File

@@ -332,8 +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);
list_del_rcu(&ops->list);
kfree_rcu(ops, rcu);
break;
}
}