net: Factorise setup_net() and cleanup_net().

When we roll back the changes made by struct pernet_operations.init(),
we execute mostly identical sequences in three places.

  * setup_net()
  * cleanup_net()
  * free_exit_list()

The only difference between the first two is which list and RCU helpers
to use.

In setup_net(), an ops could fail on the way, so we need to perform a
reverse walk from its previous ops in pernet_list.  OTOH, in cleanup_net(),
we iterate the full list from tail to head.

The former passes the failed ops to list_for_each_entry_continue_reverse().
It's tricky, but we can reuse it for the latter if we pass list_entry() of
the head node.

Also, synchronize_rcu() and synchronize_rcu_expedited() can be easily
switched by an argument.

Let's factorise the rollback part in setup_net() and cleanup_net().

In the next patch, ops_undo_list() will be reused for free_exit_list(),
and then two arguments (ops_list and hold_rtnl) will differ.

Signed-off-by: Kuniyuki Iwashima <kuniyu@amazon.com>
Reviewed-by: Sabrina Dubroca <sd@queasysnail.net>
Link: https://patch.msgid.link/20250411205258.63164-2-kuniyu@amazon.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Kuniyuki Iwashima
2025-04-11 13:52:30 -07:00
committed by Jakub Kicinski
parent ceaceaf79e
commit e333b1c3cf
+51 -55
View File
@@ -188,6 +188,53 @@ static void ops_free_list(const struct pernet_operations *ops,
}
}
static void ops_undo_list(const struct list_head *ops_list,
const struct pernet_operations *ops,
struct list_head *net_exit_list,
bool expedite_rcu, bool hold_rtnl)
{
const struct pernet_operations *saved_ops;
if (!ops)
ops = list_entry(ops_list, typeof(*ops), list);
saved_ops = ops;
list_for_each_entry_continue_reverse(ops, ops_list, list)
ops_pre_exit_list(ops, net_exit_list);
/* Another CPU might be rcu-iterating the list, wait for it.
* This needs to be before calling the exit() notifiers, so the
* rcu_barrier() after ops_undo_list() isn't sufficient alone.
* Also the pre_exit() and exit() methods need this barrier.
*/
if (expedite_rcu)
synchronize_rcu_expedited();
else
synchronize_rcu();
if (hold_rtnl) {
LIST_HEAD(dev_kill_list);
ops = saved_ops;
rtnl_lock();
list_for_each_entry_continue_reverse(ops, ops_list, list) {
if (ops->exit_batch_rtnl)
ops->exit_batch_rtnl(net_exit_list, &dev_kill_list);
}
unregister_netdevice_many(&dev_kill_list);
rtnl_unlock();
}
ops = saved_ops;
list_for_each_entry_continue_reverse(ops, ops_list, list)
ops_exit_list(ops, net_exit_list);
ops = saved_ops;
list_for_each_entry_continue_reverse(ops, ops_list, list)
ops_free_list(ops, net_exit_list);
}
/* should be called with nsid_lock held */
static int alloc_netid(struct net *net, struct net *peer, int reqid)
{
@@ -351,9 +398,8 @@ static __net_init void preinit_net(struct net *net, struct user_namespace *user_
static __net_init int setup_net(struct net *net)
{
/* Must be called with pernet_ops_rwsem held */
const struct pernet_operations *ops, *saved_ops;
const struct pernet_operations *ops;
LIST_HEAD(net_exit_list);
LIST_HEAD(dev_kill_list);
int error = 0;
preempt_disable();
@@ -376,29 +422,7 @@ out_undo:
* for the pernet modules whose init functions did not fail.
*/
list_add(&net->exit_list, &net_exit_list);
saved_ops = ops;
list_for_each_entry_continue_reverse(ops, &pernet_list, list)
ops_pre_exit_list(ops, &net_exit_list);
synchronize_rcu();
ops = saved_ops;
rtnl_lock();
list_for_each_entry_continue_reverse(ops, &pernet_list, list) {
if (ops->exit_batch_rtnl)
ops->exit_batch_rtnl(&net_exit_list, &dev_kill_list);
}
unregister_netdevice_many(&dev_kill_list);
rtnl_unlock();
ops = saved_ops;
list_for_each_entry_continue_reverse(ops, &pernet_list, list)
ops_exit_list(ops, &net_exit_list);
ops = saved_ops;
list_for_each_entry_continue_reverse(ops, &pernet_list, list)
ops_free_list(ops, &net_exit_list);
ops_undo_list(&pernet_list, ops, &net_exit_list, false, true);
rcu_barrier();
goto out;
}
@@ -594,11 +618,9 @@ struct task_struct *cleanup_net_task;
static void cleanup_net(struct work_struct *work)
{
const struct pernet_operations *ops;
struct net *net, *tmp, *last;
struct llist_node *net_kill_list;
struct net *net, *tmp, *last;
LIST_HEAD(net_exit_list);
LIST_HEAD(dev_kill_list);
cleanup_net_task = current;
@@ -629,33 +651,7 @@ static void cleanup_net(struct work_struct *work)
list_add_tail(&net->exit_list, &net_exit_list);
}
/* Run all of the network namespace pre_exit methods */
list_for_each_entry_reverse(ops, &pernet_list, list)
ops_pre_exit_list(ops, &net_exit_list);
/*
* Another CPU might be rcu-iterating the list, wait for it.
* This needs to be before calling the exit() notifiers, so
* the rcu_barrier() below isn't sufficient alone.
* Also the pre_exit() and exit() methods need this barrier.
*/
synchronize_rcu_expedited();
rtnl_lock();
list_for_each_entry_reverse(ops, &pernet_list, list) {
if (ops->exit_batch_rtnl)
ops->exit_batch_rtnl(&net_exit_list, &dev_kill_list);
}
unregister_netdevice_many(&dev_kill_list);
rtnl_unlock();
/* Run all of the network namespace exit methods */
list_for_each_entry_reverse(ops, &pernet_list, list)
ops_exit_list(ops, &net_exit_list);
/* Free the net generic variables */
list_for_each_entry_reverse(ops, &pernet_list, list)
ops_free_list(ops, &net_exit_list);
ops_undo_list(&pernet_list, NULL, &net_exit_list, true, true);
up_read(&pernet_ops_rwsem);