diff options
Diffstat (limited to 'net/core/dev.c')
| -rw-r--r-- | net/core/dev.c | 49 | 
1 files changed, 48 insertions, 1 deletions
diff --git a/net/core/dev.c b/net/core/dev.c index 5c713f2239cc..65f829cfd928 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -5247,10 +5247,12 @@ static int dev_new_index(struct net *net)  /* Delayed registration/unregisteration */  static LIST_HEAD(net_todo_list); +static DECLARE_WAIT_QUEUE_HEAD(netdev_unregistering_wq);  static void net_set_todo(struct net_device *dev)  {  	list_add_tail(&dev->todo_list, &net_todo_list); +	dev_net(dev)->dev_unreg_count++;  }  static void rollback_registered_many(struct list_head *head) @@ -5918,6 +5920,12 @@ void netdev_run_todo(void)  		if (dev->destructor)  			dev->destructor(dev); +		/* Report a network device has been unregistered */ +		rtnl_lock(); +		dev_net(dev)->dev_unreg_count--; +		__rtnl_unlock(); +		wake_up(&netdev_unregistering_wq); +  		/* Free network device */  		kobject_put(&dev->dev.kobj);  	} @@ -6603,6 +6611,34 @@ static void __net_exit default_device_exit(struct net *net)  	rtnl_unlock();  } +static void __net_exit rtnl_lock_unregistering(struct list_head *net_list) +{ +	/* Return with the rtnl_lock held when there are no network +	 * devices unregistering in any network namespace in net_list. +	 */ +	struct net *net; +	bool unregistering; +	DEFINE_WAIT(wait); + +	for (;;) { +		prepare_to_wait(&netdev_unregistering_wq, &wait, +				TASK_UNINTERRUPTIBLE); +		unregistering = false; +		rtnl_lock(); +		list_for_each_entry(net, net_list, exit_list) { +			if (net->dev_unreg_count > 0) { +				unregistering = true; +				break; +			} +		} +		if (!unregistering) +			break; +		__rtnl_unlock(); +		schedule(); +	} +	finish_wait(&netdev_unregistering_wq, &wait); +} +  static void __net_exit default_device_exit_batch(struct list_head *net_list)  {  	/* At exit all network devices most be removed from a network @@ -6614,7 +6650,18 @@ static void __net_exit default_device_exit_batch(struct list_head *net_list)  	struct net *net;  	LIST_HEAD(dev_kill_list); -	rtnl_lock(); +	/* To prevent network device cleanup code from dereferencing +	 * loopback devices or network devices that have been freed +	 * wait here for all pending unregistrations to complete, +	 * before unregistring the loopback device and allowing the +	 * network namespace be freed. +	 * +	 * The netdev todo list containing all network devices +	 * unregistrations that happen in default_device_exit_batch +	 * will run in the rtnl_unlock() at the end of +	 * default_device_exit_batch. +	 */ +	rtnl_lock_unregistering(net_list);  	list_for_each_entry(net, net_list, exit_list) {  		for_each_netdev_reverse(net, dev) {  			if (dev->rtnl_link_ops)  | 
