diff options
Diffstat (limited to 'net/core/net_namespace.c')
| -rw-r--r-- | net/core/net_namespace.c | 213 | 
1 files changed, 157 insertions, 56 deletions
diff --git a/net/core/net_namespace.c b/net/core/net_namespace.c index 3f860261c5e..85b62691f4f 100644 --- a/net/core/net_namespace.c +++ b/net/core/net_namespace.c @@ -1,3 +1,5 @@ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +  #include <linux/workqueue.h>  #include <linux/rtnetlink.h>  #include <linux/cache.h> @@ -8,6 +10,11 @@  #include <linux/idr.h>  #include <linux/rculist.h>  #include <linux/nsproxy.h> +#include <linux/fs.h> +#include <linux/proc_ns.h> +#include <linux/file.h> +#include <linux/export.h> +#include <linux/user_namespace.h>  #include <net/net_namespace.h>  #include <net/netns/generic.h> @@ -17,22 +24,30 @@  static LIST_HEAD(pernet_list);  static struct list_head *first_device = &pernet_list; -static DEFINE_MUTEX(net_mutex); +DEFINE_MUTEX(net_mutex);  LIST_HEAD(net_namespace_list);  EXPORT_SYMBOL_GPL(net_namespace_list); -struct net init_net; +struct net init_net = { +	.dev_base_head = LIST_HEAD_INIT(init_net.dev_base_head), +};  EXPORT_SYMBOL(init_net);  #define INITIAL_NET_GEN_PTRS	13 /* +1 for len +2 for rcu_head */ -static void net_generic_release(struct rcu_head *rcu) +static unsigned int max_gen_ptrs = INITIAL_NET_GEN_PTRS; + +static struct net_generic *net_alloc_generic(void)  {  	struct net_generic *ng; +	size_t generic_size = offsetof(struct net_generic, ptr[max_gen_ptrs]); -	ng = container_of(rcu, struct net_generic, rcu); -	kfree(ng); +	ng = kzalloc(generic_size, GFP_KERNEL); +	if (ng) +		ng->len = max_gen_ptrs; + +	return ng;  }  static int net_assign_generic(struct net *net, int id, void *data) @@ -48,8 +63,7 @@ static int net_assign_generic(struct net *net, int id, void *data)  	if (old_ng->len >= id)  		goto assign; -	ng = kzalloc(sizeof(struct net_generic) + -			id * sizeof(void *), GFP_KERNEL); +	ng = net_alloc_generic();  	if (ng == NULL)  		return -ENOMEM; @@ -64,11 +78,10 @@ static int net_assign_generic(struct net *net, int id, void *data)  	 * the old copy for kfree after a grace period.  	 */ -	ng->len = id;  	memcpy(&ng->ptr, &old_ng->ptr, old_ng->len * sizeof(void*));  	rcu_assign_pointer(net->gen, ng); -	call_rcu(&old_ng->rcu, net_generic_release); +	kfree_rcu(old_ng, rcu);  assign:  	ng->ptr[id - 1] = data;  	return 0; @@ -76,21 +89,29 @@ assign:  static int ops_init(const struct pernet_operations *ops, struct net *net)  { -	int err; +	int err = -ENOMEM; +	void *data = NULL; +  	if (ops->id && ops->size) { -		void *data = kzalloc(ops->size, GFP_KERNEL); +		data = kzalloc(ops->size, GFP_KERNEL);  		if (!data) -			return -ENOMEM; +			goto out;  		err = net_assign_generic(net, *ops->id, data); -		if (err) { -			kfree(data); -			return err; -		} +		if (err) +			goto cleanup;  	} +	err = 0;  	if (ops->init) -		return ops->init(net); -	return 0; +		err = ops->init(net); +	if (!err) +		return 0; + +cleanup: +	kfree(data); + +out: +	return err;  }  static void ops_free(const struct pernet_operations *ops, struct net *net) @@ -126,7 +147,7 @@ static void ops_free_list(const struct pernet_operations *ops,  /*   * setup_net runs the initializers for the network namespace object.   */ -static __net_init int setup_net(struct net *net) +static __net_init int setup_net(struct net *net, struct user_namespace *user_ns)  {  	/* Must be called with net_mutex held */  	const struct pernet_operations *ops, *saved_ops; @@ -134,6 +155,9 @@ static __net_init int setup_net(struct net *net)  	LIST_HEAD(net_exit_list);  	atomic_set(&net->count, 1); +	atomic_set(&net->passive, 1); +	net->dev_base_seq = 1; +	net->user_ns = user_ns;  #ifdef NETNS_REFCNT_DEBUG  	atomic_set(&net->use_count, 0); @@ -164,18 +188,6 @@ out_undo:  	goto out;  } -static struct net_generic *net_alloc_generic(void) -{ -	struct net_generic *ng; -	size_t generic_size = sizeof(struct net_generic) + -		INITIAL_NET_GEN_PTRS * sizeof(void *); - -	ng = kzalloc(generic_size, GFP_KERNEL); -	if (ng) -		ng->len = INITIAL_NET_GEN_PTRS; - -	return ng; -}  #ifdef CONFIG_NET_NS  static struct kmem_cache *net_cachep; @@ -207,8 +219,8 @@ static void net_free(struct net *net)  {  #ifdef NETNS_REFCNT_DEBUG  	if (unlikely(atomic_read(&net->use_count) != 0)) { -		printk(KERN_EMERG "network namespace not free! Usage: %d\n", -			atomic_read(&net->use_count)); +		pr_emerg("network namespace not free! Usage: %d\n", +			 atomic_read(&net->use_count));  		return;  	}  #endif @@ -216,16 +228,30 @@ static void net_free(struct net *net)  	kmem_cache_free(net_cachep, net);  } -static struct net *net_create(void) +void net_drop_ns(void *p) +{ +	struct net *ns = p; +	if (ns && atomic_dec_and_test(&ns->passive)) +		net_free(ns); +} + +struct net *copy_net_ns(unsigned long flags, +			struct user_namespace *user_ns, struct net *old_net)  {  	struct net *net;  	int rv; +	if (!(flags & CLONE_NEWNET)) +		return get_net(old_net); +  	net = net_alloc();  	if (!net)  		return ERR_PTR(-ENOMEM); + +	get_user_ns(user_ns); +  	mutex_lock(&net_mutex); -	rv = setup_net(net); +	rv = setup_net(net, user_ns);  	if (rv == 0) {  		rtnl_lock();  		list_add_tail_rcu(&net->list, &net_namespace_list); @@ -233,19 +259,13 @@ static struct net *net_create(void)  	}  	mutex_unlock(&net_mutex);  	if (rv < 0) { -		net_free(net); +		put_user_ns(user_ns); +		net_drop_ns(net);  		return ERR_PTR(rv);  	}  	return net;  } -struct net *copy_net_ns(unsigned long flags, struct net *old_net) -{ -	if (!(flags & CLONE_NEWNET)) -		return get_net(old_net); -	return net_create(); -} -  static DEFINE_SPINLOCK(cleanup_list_lock);  static LIST_HEAD(cleanup_list);  /* Must hold cleanup_list_lock to touch */ @@ -253,7 +273,7 @@ static void cleanup_net(struct work_struct *work)  {  	const struct pernet_operations *ops;  	struct net *net, *tmp; -	LIST_HEAD(net_kill_list); +	struct list_head net_kill_list;  	LIST_HEAD(net_exit_list);  	/* Atomically snapshot the list of namespaces to cleanup */ @@ -296,7 +316,8 @@ static void cleanup_net(struct work_struct *work)  	/* Finally it is safe to free my network namespace structure */  	list_for_each_entry_safe(net, tmp, &net_exit_list, exit_list) {  		list_del_init(&net->exit_list); -		net_free(net); +		put_user_ns(net->user_ns); +		net_drop_ns(net);  	}  }  static DECLARE_WORK(net_cleanup_work, cleanup_net); @@ -314,12 +335,30 @@ void __put_net(struct net *net)  }  EXPORT_SYMBOL_GPL(__put_net); +struct net *get_net_ns_by_fd(int fd) +{ +	struct proc_ns *ei; +	struct file *file; +	struct net *net; + +	file = proc_ns_fget(fd); +	if (IS_ERR(file)) +		return ERR_CAST(file); + +	ei = get_proc_ns(file_inode(file)); +	if (ei->ns_ops == &netns_operations) +		net = get_net(ei->ns); +	else +		net = ERR_PTR(-EINVAL); + +	fput(file); +	return net; +} +  #else -struct net *copy_net_ns(unsigned long flags, struct net *old_net) +struct net *get_net_ns_by_fd(int fd)  { -	if (flags & CLONE_NEWNET) -		return ERR_PTR(-EINVAL); -	return old_net; +	return ERR_PTR(-EINVAL);  }  #endif @@ -343,6 +382,21 @@ struct net *get_net_ns_by_pid(pid_t pid)  }  EXPORT_SYMBOL_GPL(get_net_ns_by_pid); +static __net_init int net_ns_net_init(struct net *net) +{ +	return proc_alloc_inum(&net->proc_inum); +} + +static __net_exit void net_ns_net_exit(struct net *net) +{ +	proc_free_inum(net->proc_inum); +} + +static struct pernet_operations __net_initdata net_ns_ops = { +	.init = net_ns_net_init, +	.exit = net_ns_net_exit, +}; +  static int __init net_ns_init(void)  {  	struct net_generic *ng; @@ -365,7 +419,7 @@ static int __init net_ns_init(void)  	rcu_assign_pointer(init_net.gen, ng);  	mutex_lock(&net_mutex); -	if (setup_net(&init_net)) +	if (setup_net(&init_net, &init_user_ns))  		panic("Could not setup the initial network namespace");  	rtnl_lock(); @@ -374,6 +428,8 @@ static int __init net_ns_init(void)  	mutex_unlock(&net_mutex); +	register_pernet_subsys(&net_ns_ops); +  	return 0;  } @@ -423,12 +479,7 @@ static void __unregister_pernet_operations(struct pernet_operations *ops)  static int __register_pernet_operations(struct list_head *list,  					struct pernet_operations *ops)  { -	int err = 0; -	err = ops_init(ops, &init_net); -	if (err) -		ops_free(ops, &init_net); -	return err; -	 +	return ops_init(ops, &init_net);  }  static void __unregister_pernet_operations(struct pernet_operations *ops) @@ -458,6 +509,7 @@ again:  			}  			return error;  		} +		max_gen_ptrs = max_t(unsigned int, max_gen_ptrs, *ops->id);  	}  	error = __register_pernet_operations(list, ops);  	if (error) { @@ -573,3 +625,52 @@ void unregister_pernet_device(struct pernet_operations *ops)  	mutex_unlock(&net_mutex);  }  EXPORT_SYMBOL_GPL(unregister_pernet_device); + +#ifdef CONFIG_NET_NS +static void *netns_get(struct task_struct *task) +{ +	struct net *net = NULL; +	struct nsproxy *nsproxy; + +	rcu_read_lock(); +	nsproxy = task_nsproxy(task); +	if (nsproxy) +		net = get_net(nsproxy->net_ns); +	rcu_read_unlock(); + +	return net; +} + +static void netns_put(void *ns) +{ +	put_net(ns); +} + +static int netns_install(struct nsproxy *nsproxy, void *ns) +{ +	struct net *net = ns; + +	if (!ns_capable(net->user_ns, CAP_SYS_ADMIN) || +	    !ns_capable(current_user_ns(), CAP_SYS_ADMIN)) +		return -EPERM; + +	put_net(nsproxy->net_ns); +	nsproxy->net_ns = get_net(net); +	return 0; +} + +static unsigned int netns_inum(void *ns) +{ +	struct net *net = ns; +	return net->proc_inum; +} + +const struct proc_ns_operations netns_operations = { +	.name		= "net", +	.type		= CLONE_NEWNET, +	.get		= netns_get, +	.put		= netns_put, +	.install	= netns_install, +	.inum		= netns_inum, +}; +#endif  | 
