summaryrefslogtreecommitdiff
path: root/net/core/dev.c
diff options
context:
space:
mode:
Diffstat (limited to 'net/core/dev.c')
-rw-r--r--net/core/dev.c140
1 files changed, 138 insertions, 2 deletions
diff --git a/net/core/dev.c b/net/core/dev.c
index 5933c5dab09e..c1c1be1a6962 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -1802,6 +1802,7 @@ void netif_close_many(struct list_head *head, bool unlink)
__dev_close_many(head);
list_for_each_entry_safe(dev, tmp, head, close_list) {
+ netdev_assert_locked_ops_compat(dev);
rtmsg_ifinfo(RTM_NEWLINK, dev, IFF_UP | IFF_RUNNING, GFP_KERNEL, 0, NULL);
call_netdevice_notifiers(NETDEV_DOWN, dev);
if (unlink)
@@ -1912,9 +1913,11 @@ static void call_netdevice_unregister_notifiers(struct notifier_block *nb,
struct net_device *dev)
{
if (dev->flags & IFF_UP) {
+ netdev_lock_ops(dev);
call_netdevice_notifier(nb, NETDEV_GOING_DOWN,
dev);
call_netdevice_notifier(nb, NETDEV_DOWN, dev);
+ netdev_unlock_ops(dev);
}
call_netdevice_notifier(nb, NETDEV_UNREGISTER, dev);
}
@@ -9791,6 +9794,8 @@ void __dev_notify_flags(struct net_device *dev, unsigned int old_flags,
{
unsigned int changes = dev->flags ^ old_flags;
+ netdev_assert_locked_ops_compat(dev);
+
if (gchanges)
rtmsg_ifinfo(RTM_NEWLINK, dev, gchanges, GFP_ATOMIC, portid, nlh);
@@ -11614,8 +11619,13 @@ static struct net_device *netdev_wait_allrefs_any(struct list_head *list)
rtnl_lock();
/* Rebroadcast unregister notification */
- list_for_each_entry(dev, list, todo_list)
+ list_for_each_entry(dev, list, todo_list) {
+ struct net *net = dev_net(dev);
+
+ __rtnl_net_lock(net);
call_netdevice_notifiers(NETDEV_UNREGISTER, dev);
+ __rtnl_net_unlock(net);
+ }
__rtnl_unlock();
rcu_barrier();
@@ -12093,6 +12103,9 @@ struct net_device *alloc_netdev_mqs(int sizeof_priv, const char *name,
INIT_LIST_HEAD(&dev->napi_list);
INIT_LIST_HEAD(&dev->unreg_list);
+#ifdef CONFIG_DEBUG_NET_SMALL_RTNL
+ INIT_LIST_HEAD(&dev->unreg_list_net);
+#endif
INIT_LIST_HEAD(&dev->close_list);
INIT_LIST_HEAD(&dev->link_watch_list);
INIT_LIST_HEAD(&dev->adj_list.upper);
@@ -12310,6 +12323,10 @@ void unregister_netdevice_queue(struct net_device *dev, struct list_head *head)
{
ASSERT_RTNL();
+#ifdef CONFIG_DEBUG_NET_SMALL_RTNL
+ DEBUG_NET_WARN_ON_ONCE(!list_empty(&dev->unreg_list_net));
+#endif
+
if (head) {
list_move_tail(&dev->unreg_list, head);
} else {
@@ -12486,6 +12503,16 @@ void unregister_netdevice_many_notify(struct list_head *head,
synchronize_net();
list_for_each_entry(dev, head, unreg_list) {
+#ifdef CONFIG_DEBUG_NET_SMALL_RTNL
+ struct net *net = dev_net(dev);
+
+ /* spin_lock() can be moved outside of the loop
+ * once the per-netns RTNL conversion completes.
+ */
+ spin_lock(&net->dev_unreg_lock);
+ list_del(&dev->unreg_list_net);
+ spin_unlock(&net->dev_unreg_lock);
+#endif
netdev_put(dev, &dev->dev_registered_tracker);
net_set_todo(dev);
cnt++;
@@ -12508,6 +12535,96 @@ void unregister_netdevice_many(struct list_head *head)
}
EXPORT_SYMBOL(unregister_netdevice_many);
+#ifdef CONFIG_DEBUG_NET_SMALL_RTNL
+void unregister_netdevice_queue_net(struct net *net, struct net_device *dev,
+ struct list_head *head)
+{
+ netdev_lock(dev);
+
+ if (net_eq(dev_net(dev), net)) {
+ netdev_unlock(dev);
+ unregister_netdevice_queue(dev, head);
+ return;
+ }
+
+ net = dev_net(dev);
+
+ spin_lock(&net->dev_unreg_lock);
+
+ DEBUG_NET_WARN_ON_ONCE(!list_empty(&dev->unreg_list));
+ DEBUG_NET_WARN_ON_ONCE(!list_empty(&dev->unreg_list_net));
+
+ list_add_tail(&dev->unreg_list_net, &net->dev_unreg_head);
+ rtnl_net_queue_work(net);
+
+ spin_unlock(&net->dev_unreg_lock);
+
+ netdev_unlock(dev);
+}
+EXPORT_SYMBOL(unregister_netdevice_queue_net);
+
+void unregister_netdevice_queue_many_net(struct net *net, struct list_head *head)
+{
+ struct net_device *dev, *tmp;
+
+ spin_lock(&net->dev_unreg_lock);
+ list_for_each_entry_safe(dev, tmp, head, unreg_list) {
+ /* Once all cross-netns unregister_netdevice_queue() is
+ * converted to _net() (or for debugging), remove this check.
+ */
+ if (!net_eq(dev_net(dev), net))
+ continue;
+
+ DEBUG_NET_WARN_ONCE(!net_eq(dev_net(dev), net),
+ "%s was unregistered from a different netns.\n",
+ dev->name);
+
+ list_del_init(&dev->unreg_list);
+ list_move_tail(&dev->unreg_list_net, &net->dev_unreg_head);
+ }
+ spin_unlock(&net->dev_unreg_lock);
+}
+
+static void unregister_netdevice_move_net(struct net *net_old,
+ struct net *net,
+ struct net_device *dev)
+{
+ if (net_old > net) {
+ spin_lock(&net->dev_unreg_lock);
+ spin_lock_nested(&net_old->dev_unreg_lock, SINGLE_DEPTH_NESTING);
+ } else {
+ spin_lock(&net_old->dev_unreg_lock);
+ spin_lock_nested(&net->dev_unreg_lock, SINGLE_DEPTH_NESTING);
+ }
+
+ if (!list_empty(&dev->unreg_list_net)) {
+ list_del(&dev->unreg_list_net);
+ list_add_tail(&dev->unreg_list_net, &net->dev_unreg_head);
+ }
+
+ spin_unlock(&net_old->dev_unreg_lock);
+ spin_unlock(&net->dev_unreg_lock);
+}
+
+void unregister_netdevice_many_net(struct net *net)
+{
+ struct net_device *dev, *tmp;
+ LIST_HEAD(unreg_head_net);
+ LIST_HEAD(unreg_head);
+
+ spin_lock(&net->dev_unreg_lock);
+ list_splice_init(&net->dev_unreg_head, &unreg_head_net);
+ spin_unlock(&net->dev_unreg_lock);
+
+ list_for_each_entry_safe(dev, tmp, &unreg_head_net, unreg_list_net) {
+ list_del_init(&dev->unreg_list_net);
+ list_add_tail(&dev->unreg_list, &unreg_head);
+ }
+
+ unregister_netdevice_many(&unreg_head);
+}
+#endif
+
/**
* unregister_netdev - remove device from the kernel
* @dev: device
@@ -12664,6 +12781,10 @@ int __dev_change_net_namespace(struct net_device *dev, struct net *net,
netdev_unlock(dev);
dev->ifindex = new_ifindex;
+#ifdef CONFIG_DEBUG_NET_SMALL_RTNL
+ unregister_netdevice_move_net(net_old, net, dev);
+#endif
+
if (new_name[0]) {
/* Rename the netdev to prepared name */
write_seqlock_bh(&netdev_rename_lock);
@@ -13040,7 +13161,7 @@ static void __net_exit default_device_exit_net(struct net *net)
* Push all migratable network devices back to the
* initial network namespace
*/
- ASSERT_RTNL();
+
for_each_netdev_safe(net, dev, aux) {
int err;
char fb_name[IFNAMSIZ];
@@ -13083,21 +13204,36 @@ static void __net_exit default_device_exit_batch(struct list_head *net_list)
LIST_HEAD(dev_kill_list);
rtnl_lock();
+
+ __rtnl_net_lock(&init_net);
+
list_for_each_entry(net, net_list, exit_list) {
+ __rtnl_net_lock(net);
default_device_exit_net(net);
+ __rtnl_net_unlock(net);
+
cond_resched();
}
+ __rtnl_net_unlock(&init_net);
+
list_for_each_entry(net, net_list, exit_list) {
+ __rtnl_net_lock(net);
+
for_each_netdev_reverse(net, dev) {
if (dev->rtnl_link_ops && dev->rtnl_link_ops->dellink)
dev->rtnl_link_ops->dellink(dev, &dev_kill_list);
else
unregister_netdevice_queue(dev, &dev_kill_list);
}
+
+ unregister_netdevice_queue_many_net(net, &dev_kill_list);
+ __rtnl_net_unlock(net);
}
unregister_netdevice_many(&dev_kill_list);
rtnl_unlock();
+
+ rtnl_net_flush_workqueue();
}
static struct pernet_operations __net_initdata default_device_ops = {