Hook into new devices if their name matches the hook spec. Signed-off-by: Phil Sutter <phil@xxxxxx> --- 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/nft_chain_filter.c | 50 +++++++++++++++++++++++++------- 1 file changed, 39 insertions(+), 11 deletions(-) diff --git a/net/netfilter/nft_chain_filter.c b/net/netfilter/nft_chain_filter.c index f8c69d28d656..562af2773a66 100644 --- a/net/netfilter/nft_chain_filter.c +++ b/net/netfilter/nft_chain_filter.c @@ -318,23 +318,47 @@ static const struct nft_chain_type nft_chain_filter_netdev = { }, }; -static void nft_netdev_event(unsigned long event, struct net_device *dev, - struct nft_ctx *ctx) +static int nft_netdev_event(unsigned long event, struct net_device *dev, + struct nft_ctx *ctx) { struct nft_base_chain *basechain = nft_base_chain(ctx->chain); 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 (!(ctx->chain->table->flags & NFT_TABLE_F_DORMANT)) + nf_unregister_net_hook(ctx->net, ops); + list_del_rcu(&ops->list); + kfree_rcu(ops, rcu); + break; + case NETDEV_REGISTER: + if (strcmp(hook->ifname, dev->name)) + continue; - if (!(ctx->chain->table->flags & NFT_TABLE_F_DORMANT)) - nf_unregister_net_hook(ctx->net, ops); - list_del_rcu(&ops->list); - kfree_rcu(ops, rcu); + ops = kmemdup(&basechain->ops, + sizeof(struct nf_hook_ops), + GFP_KERNEL_ACCOUNT); + if (!ops) + return 1; + + ops->dev = dev; + + if (!(ctx->chain->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, @@ -349,7 +373,8 @@ static int nf_tables_netdev_event(struct notifier_block *this, .net = dev_net(dev), }; - if (event != NETDEV_UNREGISTER) + if (event != NETDEV_REGISTER && + event != NETDEV_UNREGISTER) return NOTIFY_DONE; nft_net = nft_pernet(ctx.net); @@ -371,7 +396,10 @@ static int nf_tables_netdev_event(struct notifier_block *this, continue; ctx.chain = chain; - nft_netdev_event(event, dev, &ctx); + if (nft_netdev_event(event, dev, &ctx)) { + mutex_unlock(&nft_net->commit_mutex); + return NOTIFY_BAD; + } } } mutex_unlock(&nft_net->commit_mutex); -- 2.43.0