summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--include/net/net_namespace.h2
-rw-r--r--net/core/net_namespace.c66
2 files changed, 59 insertions, 9 deletions
diff --git a/include/net/net_namespace.h b/include/net/net_namespace.h
index 0addd45038a..d69b4796030 100644
--- a/include/net/net_namespace.h
+++ b/include/net/net_namespace.h
@@ -42,7 +42,7 @@ struct net {
*/
#endif
struct list_head list; /* list of network namespaces */
- struct work_struct work; /* work struct for freeing */
+ struct list_head cleanup_list; /* namespaces on death row */
struct proc_dir_entry *proc_net;
struct proc_dir_entry *proc_net_stat;
diff --git a/net/core/net_namespace.c b/net/core/net_namespace.c
index 86ed7f44d08..a42caa2b909 100644
--- a/net/core/net_namespace.c
+++ b/net/core/net_namespace.c
@@ -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);