Commit e333b1c3 authored by Kuniyuki Iwashima's avatar Kuniyuki Iwashima Committed by Jakub Kicinski
Browse files

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: default avatarKuniyuki Iwashima <kuniyu@amazon.com>
Reviewed-by: default avatarSabrina Dubroca <sd@queasysnail.net>
Link: https://patch.msgid.link/20250411205258.63164-2-kuniyu@amazon.com


Signed-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parent ceaceaf7
Loading
Loading
Loading
Loading
+51 −55
Original line number Diff line number Diff line
@@ -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 @@ static __net_init int setup_net(struct net *net)
	 * 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);