netfilter: nf_tables: Respect NETDEV_REGISTER events
authorPhil Sutter <phil@nwl.cc>
Wed, 21 May 2025 20:44:28 +0000 (22:44 +0200)
committerPablo Neira Ayuso <pablo@netfilter.org>
Fri, 23 May 2025 11:57:13 +0000 (13:57 +0200)
Hook into new devices if their name matches the hook spec.

Signed-off-by: Phil Sutter <phil@nwl.cc>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
net/netfilter/nf_tables_api.c
net/netfilter/nft_chain_filter.c

index 95b43499f551987d32b11dc856c3d00c6c29a880..f0f40aeccc0c242dc2d62d66c18f9bfd14fe352a 100644 (file)
@@ -9689,8 +9689,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;
@@ -9708,9 +9708,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,
@@ -9722,15 +9745,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);
index 2eee78b58123bb03b78235cd8f1cb2fd73ab5208..58000b3893ebe1c6fc0d39e5889a3393bb77cb6c 100644 (file)
@@ -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,9 +338,29 @@ 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;
                }
                break;
        }
+       return 0;
 }
 
 static int nf_tables_netdev_event(struct notifier_block *this,
@@ -352,7 +372,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));
@@ -371,7 +392,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);