net: Batch network namespace destruction.
[safe/jmp/linux-2.6] / net / core / net_namespace.c
index 86ed7f4..a42caa2 100644 (file)
@@ -8,8 +8,10 @@
 #include <linux/idr.h>
 #include <linux/rculist.h>
 #include <linux/nsproxy.h>
+#include <linux/netdevice.h>
 #include <net/net_namespace.h>
 #include <net/netns/generic.h>
+#include <net/rtnetlink.h>
 
 /*
  *     Our network namespace constructor/destructor lists
@@ -27,6 +29,20 @@ EXPORT_SYMBOL(init_net);
 
 #define INITIAL_NET_GEN_PTRS   13 /* +1 for len +2 for rcu_head */
 
+static void unregister_netdevices(struct net *net, struct list_head *list)
+{
+       struct net_device *dev;
+       /* At exit all network devices most be removed from a network
+        * namespace.  Do this in the reverse order of registeration.
+        */
+       for_each_netdev_reverse(net, dev) {
+               if (dev->rtnl_link_ops)
+                       dev->rtnl_link_ops->dellink(dev, list);
+               else
+                       unregister_netdevice_queue(dev, list);
+       }
+}
+
 /*
  * setup_net runs the initializers for the network namespace object.
  */
@@ -59,6 +75,13 @@ out_undo:
        list_for_each_entry_continue_reverse(ops, &pernet_list, list) {
                if (ops->exit)
                        ops->exit(net);
+               if (&ops->list == first_device) {
+                       LIST_HEAD(dev_kill_list);
+                       rtnl_lock();
+                       unregister_netdevices(net, &dev_kill_list);
+                       unregister_netdevice_many(&dev_kill_list);
+                       rtnl_unlock();
+               }
        }
 
        rcu_barrier();
@@ -147,18 +170,26 @@ struct net *copy_net_ns(unsigned long flags, struct net *old_net)
        return net_create();
 }
 
+static DEFINE_SPINLOCK(cleanup_list_lock);
+static LIST_HEAD(cleanup_list);  /* Must hold cleanup_list_lock to touch */
+
 static void cleanup_net(struct work_struct *work)
 {
        struct pernet_operations *ops;
-       struct net *net;
+       struct net *net, *tmp;
+       LIST_HEAD(net_kill_list);
 
-       net = container_of(work, struct net, work);
+       /* Atomically snapshot the list of namespaces to cleanup */
+       spin_lock_irq(&cleanup_list_lock);
+       list_replace_init(&cleanup_list, &net_kill_list);
+       spin_unlock_irq(&cleanup_list_lock);
 
        mutex_lock(&net_mutex);
 
        /* Don't let anyone else find us. */
        rtnl_lock();
-       list_del_rcu(&net->list);
+       list_for_each_entry(net, &net_kill_list, cleanup_list)
+               list_del_rcu(&net->list);
        rtnl_unlock();
 
        /*
@@ -170,8 +201,18 @@ static void cleanup_net(struct work_struct *work)
 
        /* Run all of the network namespace exit methods */
        list_for_each_entry_reverse(ops, &pernet_list, list) {
-               if (ops->exit)
-                       ops->exit(net);
+               if (ops->exit) {
+                       list_for_each_entry(net, &net_kill_list, cleanup_list)
+                               ops->exit(net);
+               }
+               if (&ops->list == first_device) {
+                       LIST_HEAD(dev_kill_list);
+                       rtnl_lock();
+                       list_for_each_entry(net, &net_kill_list, cleanup_list)
+                               unregister_netdevices(net, &dev_kill_list);
+                       unregister_netdevice_many(&dev_kill_list);
+                       rtnl_unlock();
+               }
        }
 
        mutex_unlock(&net_mutex);
@@ -182,14 +223,23 @@ static void cleanup_net(struct work_struct *work)
        rcu_barrier();
 
        /* Finally it is safe to free my network namespace structure */
-       net_free(net);
+       list_for_each_entry_safe(net, tmp, &net_kill_list, cleanup_list) {
+               list_del_init(&net->cleanup_list);
+               net_free(net);
+       }
 }
+static DECLARE_WORK(net_cleanup_work, cleanup_net);
 
 void __put_net(struct net *net)
 {
        /* Cleanup the network namespace in process context */
-       INIT_WORK(&net->work, cleanup_net);
-       queue_work(netns_wq, &net->work);
+       unsigned long flags;
+
+       spin_lock_irqsave(&cleanup_list_lock, flags);
+       list_add(&net->cleanup_list, &cleanup_list);
+       spin_unlock_irqrestore(&cleanup_list_lock, flags);
+
+       queue_work(netns_wq, &net_cleanup_work);
 }
 EXPORT_SYMBOL_GPL(__put_net);