cregit-Linux how code gets into the kernel

Release 4.11 net/core/net_namespace.c

Directory: net/core

#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/workqueue.h>
#include <linux/rtnetlink.h>
#include <linux/cache.h>
#include <linux/slab.h>
#include <linux/list.h>
#include <linux/delay.h>
#include <linux/sched.h>
#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 <linux/net_namespace.h>
#include <linux/sched/task.h>

#include <net/sock.h>
#include <net/netlink.h>
#include <net/net_namespace.h>
#include <net/netns/generic.h>

/*
 *      Our network namespace constructor/destructor lists
 */

static LIST_HEAD(pernet_list);

static struct list_head *first_device = &pernet_list;

DEFINE_MUTEX(net_mutex);


LIST_HEAD(net_namespace_list);

EXPORT_SYMBOL_GPL(net_namespace_list);


struct net init_net = {
	.dev_base_head = LIST_HEAD_INIT(init_net.dev_base_head),
};

EXPORT_SYMBOL(init_net);


static bool init_net_initialized;


#define MIN_PERNET_OPS_ID	\
	((sizeof(struct net_generic) + sizeof(void *) - 1) / sizeof(void *))


#define INITIAL_NET_GEN_PTRS	13 
/* +1 for len +2 for rcu_head */


static unsigned int max_gen_ptrs = INITIAL_NET_GEN_PTRS;


static struct net_generic *net_alloc_generic(void) { struct net_generic *ng; unsigned int generic_size = offsetof(struct net_generic, ptr[max_gen_ptrs]); ng = kzalloc(generic_size, GFP_KERNEL); if (ng) ng->s.len = max_gen_ptrs; return ng; }

Contributors

PersonTokensPropCommitsCommitProp
Eric Dumazet5092.59%133.33%
Alexey Dobriyan47.41%266.67%
Total54100.00%3100.00%


static int net_assign_generic(struct net *net, unsigned int id, void *data) { struct net_generic *ng, *old_ng; BUG_ON(!mutex_is_locked(&net_mutex)); BUG_ON(id < MIN_PERNET_OPS_ID); old_ng = rcu_dereference_protected(net->gen, lockdep_is_held(&net_mutex)); if (old_ng->s.len > id) { old_ng->ptr[id] = data; return 0; } ng = net_alloc_generic(); if (ng == NULL) return -ENOMEM; /* * Some synchronisation notes: * * The net_generic explores the net->gen array inside rcu * read section. Besides once set the net->gen->ptr[x] * pointer never changes (see rules in netns/generic.h). * * That said, we simply duplicate this array and schedule * the old copy for kfree after a grace period. */ memcpy(&ng->ptr[MIN_PERNET_OPS_ID], &old_ng->ptr[MIN_PERNET_OPS_ID], (old_ng->s.len - MIN_PERNET_OPS_ID) * sizeof(void *)); ng->ptr[id] = data; rcu_assign_pointer(net->gen, ng); kfree_rcu(old_ng, s.rcu); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Jiri Pirko10765.24%112.50%
Alexey Dobriyan4326.22%450.00%
Eric Dumazet127.32%225.00%
Lai Jiangshan21.22%112.50%
Total164100.00%8100.00%


static int ops_init(const struct pernet_operations *ops, struct net *net) { int err = -ENOMEM; void *data = NULL; if (ops->id && ops->size) { data = kzalloc(ops->size, GFP_KERNEL); if (!data) goto out; err = net_assign_generic(net, *ops->id, data); if (err) goto cleanup; } err = 0; if (ops->init) err = ops->init(net); if (!err) return 0; cleanup: kfree(data); out: return err; }

Contributors

PersonTokensPropCommitsCommitProp
Eric W. Biedermann7965.83%150.00%
Julian Anastasov4134.17%150.00%
Total120100.00%2100.00%


static void ops_free(const struct pernet_operations *ops, struct net *net) { if (ops->id && ops->size) { kfree(net_generic(net, *ops->id)); } }

Contributors

PersonTokensPropCommitsCommitProp
Eric W. Biedermann3992.86%150.00%
Alexey Dobriyan37.14%150.00%
Total42100.00%2100.00%


static void ops_exit_list(const struct pernet_operations *ops, struct list_head *net_exit_list) { struct net *net; if (ops->exit) { list_for_each_entry(net, net_exit_list, exit_list) ops->exit(net); } if (ops->exit_batch) ops->exit_batch(net_exit_list); }

Contributors

PersonTokensPropCommitsCommitProp
Eric W. Biedermann58100.00%1100.00%
Total58100.00%1100.00%


static void ops_free_list(const struct pernet_operations *ops, struct list_head *net_exit_list) { struct net *net; if (ops->size && ops->id) { list_for_each_entry(net, net_exit_list, exit_list) ops_free(ops, net); } }

Contributors

PersonTokensPropCommitsCommitProp
Eric W. Biedermann49100.00%1100.00%
Total49100.00%1100.00%

/* should be called with nsid_lock held */
static int alloc_netid(struct net *net, struct net *peer, int reqid) { int min = 0, max = 0; if (reqid >= 0) { min = reqid; max = reqid + 1; } return idr_alloc(&net->netns_ids, peer, min, max, GFP_ATOMIC); }

Contributors

PersonTokensPropCommitsCommitProp
Nicolas Dichtel63100.00%3100.00%
Total63100.00%3100.00%

/* This function is used by idr_for_each(). If net is equal to peer, the * function returns the id so that idr_for_each() stops. Because we cannot * returns the id 0 (idr_for_each() will not stop), we return the magic value * NET_ID_ZERO (-1) for it. */ #define NET_ID_ZERO -1
static int net_eq_idr(int id, void *net, void *peer) { if (net_eq(net, peer)) return id ? : NET_ID_ZERO; return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Nicolas Dichtel35100.00%1100.00%
Total35100.00%1100.00%

/* Should be called with nsid_lock held. If a new id is assigned, the bool alloc * is set to true, thus the caller knows that the new id must be notified via * rtnl. */
static int __peernet2id_alloc(struct net *net, struct net *peer, bool *alloc) { int id = idr_for_each(&net->netns_ids, net_eq_idr, peer); bool alloc_it = *alloc; *alloc = false; /* Magic value for id 0. */ if (id == NET_ID_ZERO) return 0; if (id > 0) return id; if (alloc_it) { id = alloc_netid(net, peer, -1); *alloc = true; return id >= 0 ? id : NETNSA_NSID_NOT_ASSIGNED; } return NETNSA_NSID_NOT_ASSIGNED; }

Contributors

PersonTokensPropCommitsCommitProp
Nicolas Dichtel100100.00%3100.00%
Total100100.00%3100.00%

/* should be called with nsid_lock held */
static int __peernet2id(struct net *net, struct net *peer) { bool no = false; return __peernet2id_alloc(net, peer, &no); }

Contributors

PersonTokensPropCommitsCommitProp
Nicolas Dichtel32100.00%1100.00%
Total32100.00%1100.00%

static void rtnl_net_notifyid(struct net *net, int cmd, int id); /* This function returns the id of a peer netns. If no id is assigned, one will * be allocated and returned. */
int peernet2id_alloc(struct net *net, struct net *peer) { bool alloc; int id; if (atomic_read(&net->count) == 0) return NETNSA_NSID_NOT_ASSIGNED; spin_lock_bh(&net->nsid_lock); alloc = atomic_read(&peer->count) == 0 ? false : true; id = __peernet2id_alloc(net, peer, &alloc); spin_unlock_bh(&net->nsid_lock); if (alloc && id >= 0) rtnl_net_notifyid(net, RTM_NEWNSID, id); return id; }

Contributors

PersonTokensPropCommitsCommitProp
Nicolas Dichtel7979.00%562.50%
Américo Wang1919.00%225.00%
Paul Moore22.00%112.50%
Total100100.00%8100.00%

/* This function returns, if assigned, the id of a peer netns. */
int peernet2id(struct net *net, struct net *peer) { int id; spin_lock_bh(&net->nsid_lock); id = __peernet2id(net, peer); spin_unlock_bh(&net->nsid_lock); return id; }

Contributors

PersonTokensPropCommitsCommitProp
Nicolas Dichtel4086.96%133.33%
Américo Wang48.70%133.33%
Paul Moore24.35%133.33%
Total46100.00%3100.00%

EXPORT_SYMBOL(peernet2id); /* This function returns true is the peer netns has an id assigned into the * current netns. */
bool peernet_has_id(struct net *net, struct net *peer) { return peernet2id(net, peer) >= 0; }

Contributors

PersonTokensPropCommitsCommitProp
Nicolas Dichtel25100.00%1100.00%
Total25100.00%1100.00%


struct net *get_net_ns_by_id(struct net *net, int id) { struct net *peer; if (id < 0) return NULL; rcu_read_lock(); spin_lock_bh(&net->nsid_lock); peer = idr_find(&net->netns_ids, id); if (peer) get_net(peer); spin_unlock_bh(&net->nsid_lock); rcu_read_unlock(); return peer; }

Contributors

PersonTokensPropCommitsCommitProp
Nicolas Dichtel6992.00%250.00%
Américo Wang45.33%125.00%
Paul Moore22.67%125.00%
Total75100.00%4100.00%

/* * setup_net runs the initializers for the network namespace object. */
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; int error = 0; 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; idr_init(&net->netns_ids); spin_lock_init(&net->nsid_lock); list_for_each_entry(ops, &pernet_list, list) { error = ops_init(ops, net); if (error < 0) goto out_undo; } out: return error; out_undo: /* Walk through the list backwards calling the exit functions * for the pernet modules whose init functions did not fail. */ list_add(&net->exit_list, &net_exit_list); saved_ops = ops; list_for_each_entry_continue_reverse(ops, &pernet_list, list) ops_exit_list(ops, &net_exit_list); ops = saved_ops; list_for_each_entry_continue_reverse(ops, &pernet_list, list) ops_free_list(ops, &net_exit_list); rcu_barrier(); goto out; }

Contributors

PersonTokensPropCommitsCommitProp
Eric W. Biedermann13173.60%535.71%
Al Viro105.62%17.14%
Pavel Emelyanov105.62%321.43%
Américo Wang84.49%17.14%
Nicolas Dichtel84.49%17.14%
Thomas Graf63.37%17.14%
Daniel Lezcano52.81%214.29%
Total178100.00%14100.00%

#ifdef CONFIG_NET_NS
static struct ucounts *inc_net_namespaces(struct user_namespace *ns) { return inc_ucount(ns, current_euid(), UCOUNT_NET_NAMESPACES); }

Contributors

PersonTokensPropCommitsCommitProp
Arnd Bergmann24100.00%1100.00%
Total24100.00%1100.00%


static void dec_net_namespaces(struct ucounts *ucounts) { dec_ucount(ucounts, UCOUNT_NET_NAMESPACES); }

Contributors

PersonTokensPropCommitsCommitProp
Arnd Bergmann18100.00%1100.00%
Total18100.00%1100.00%

static struct kmem_cache *net_cachep; static struct workqueue_struct *netns_wq;
static struct net *net_alloc(void) { struct net *net = NULL; struct net_generic *ng; ng = net_alloc_generic(); if (!ng) goto out; net = kmem_cache_zalloc(net_cachep, GFP_KERNEL); if (!net) goto out_free; rcu_assign_pointer(net->gen, ng); out: return net; out_free: kfree(ng); goto out; }

Contributors

PersonTokensPropCommitsCommitProp
Daniel Lezcano5977.63%150.00%
Pavel Emelyanov1722.37%150.00%
Total76100.00%2100.00%


static void net_free(struct net *net) { kfree(rcu_access_pointer(net->gen)); kmem_cache_free(net_cachep, net); }

Contributors

PersonTokensPropCommitsCommitProp
Johann Felix Soden1864.29%133.33%
Alexey Dobriyan725.00%133.33%
Eric Dumazet310.71%133.33%
Total28100.00%3100.00%


void net_drop_ns(void *p) { struct net *ns = p; if (ns && atomic_dec_and_test(&ns->passive)) net_free(ns); }

Contributors

PersonTokensPropCommitsCommitProp
Al Viro33100.00%1100.00%
Total33100.00%1100.00%


struct net *copy_net_ns(unsigned long flags, struct user_namespace *user_ns, struct net *old_net) { struct ucounts *ucounts; struct net *net; int rv; if (!(flags & CLONE_NEWNET)) return get_net(old_net); ucounts = inc_net_namespaces(user_ns); if (!ucounts) return ERR_PTR(-ENOSPC); net = net_alloc(); if (!net) { dec_net_namespaces(ucounts); return ERR_PTR(-ENOMEM); } get_user_ns(user_ns); rv = mutex_lock_killable(&net_mutex); if (rv < 0) { net_free(net); dec_net_namespaces(ucounts); put_user_ns(user_ns); return ERR_PTR(rv); } net->ucounts = ucounts; rv = setup_net(net, user_ns); if (rv == 0) { rtnl_lock(); list_add_tail_rcu(&net->list, &net_namespace_list); rtnl_unlock(); } mutex_unlock(&net_mutex); if (rv < 0) { dec_net_namespaces(ucounts); put_user_ns(user_ns); net_drop_ns(net); return ERR_PTR(rv); } return net; }

Contributors

PersonTokensPropCommitsCommitProp
Eric W. Biedermann11956.13%545.45%
Andrey Vagin3215.09%19.09%
Alexey Dobriyan3014.15%19.09%
Rob Landley2411.32%19.09%
Daniel Lezcano52.36%19.09%
Al Viro10.47%19.09%
Johannes Berg10.47%19.09%
Total212100.00%11100.00%

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) { const struct pernet_operations *ops; struct net *net, *tmp; struct list_head net_kill_list; LIST_HEAD(net_exit_list); /* 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_for_each_entry(net, &net_kill_list, cleanup_list) { list_del_rcu(&net->list); list_add_tail(&net->exit_list, &net_exit_list); for_each_net(tmp) { int id; spin_lock_bh(&tmp->nsid_lock); id = __peernet2id(tmp, net); if (id >= 0) idr_remove(&tmp->netns_ids, id); spin_unlock_bh(&tmp->nsid_lock); if (id >= 0) rtnl_net_notifyid(tmp, RTM_DELNSID, id); } spin_lock_bh(&net->nsid_lock); idr_destroy(&net->netns_ids); spin_unlock_bh(&net->nsid_lock); } rtnl_unlock(); /* * Another CPU might be rcu-iterating the list, wait for it. * This needs to be before calling the exit() notifiers, so * the rcu_barrier() below isn't sufficient alone. */ synchronize_rcu(); /* Run all of the network namespace exit methods */ list_for_each_entry_reverse(ops, &pernet_list, list) ops_exit_list(ops, &net_exit_list); /* Free the net generic variables */ list_for_each_entry_reverse(ops, &pernet_list, list) ops_free_list(ops, &net_exit_list); mutex_unlock(&net_mutex); /* Ensure there are no outstanding rcu callbacks using this * network namespace. */ rcu_barrier(); /* 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); dec_net_namespaces(net->ucounts); put_user_ns(net->user_ns); net_drop_ns(net); } }

Contributors

PersonTokensPropCommitsCommitProp
Eric W. Biedermann10839.27%535.71%
Nicolas Dichtel7728.00%321.43%
Pavel Emelyanov6925.09%17.14%
Américo Wang82.91%17.14%
Johannes Berg51.82%17.14%
Paul Moore41.45%17.14%
xiao jin31.09%17.14%
Al Viro10.36%17.14%
Total275100.00%14100.00%

static DECLARE_WORK(net_cleanup_work, cleanup_net);
void __put_net(struct net *net) { /* Cleanup the network namespace in process context */ 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); }

Contributors

PersonTokensPropCommitsCommitProp
Eric W. Biedermann2550.00%133.33%
Pavel Emelyanov2244.00%133.33%
Benjamin Thery36.00%133.33%
Total50100.00%3100.00%

EXPORT_SYMBOL_GPL(__put_net);
struct net *get_net_ns_by_fd(int fd) { struct file *file; struct ns_common *ns; struct net *net; file = proc_ns_fget(fd); if (IS_ERR(file)) return ERR_CAST(file); ns = get_proc_ns(file_inode(file)); if (ns->ops == &netns_operations) net = get_net(container_of(ns, struct net, ns)); else net = ERR_PTR(-EINVAL); fput(file); return net; }

Contributors

PersonTokensPropCommitsCommitProp
Stephen Rothwell5759.38%114.29%
Al Viro3839.58%571.43%
David Howells11.04%114.29%
Total96100.00%7100.00%

#else
struct net *get_net_ns_by_fd(int fd) { return ERR_PTR(-EINVAL); }

Contributors

PersonTokensPropCommitsCommitProp
Stephen Rothwell17100.00%1100.00%
Total17100.00%1100.00%

#endif EXPORT_SYMBOL_GPL(get_net_ns_by_fd);
struct net *get_net_ns_by_pid(pid_t pid) { struct task_struct *tsk; struct net *net; /* Lookup the network namespace */ net = ERR_PTR(-ESRCH); rcu_read_lock(); tsk = find_task_by_vpid(pid); if (tsk) { struct nsproxy *nsproxy; task_lock(tsk); nsproxy = tsk->nsproxy; if (nsproxy) net = get_net(nsproxy->net_ns); task_unlock(tsk); } rcu_read_unlock(); return net; }

Contributors

PersonTokensPropCommitsCommitProp
Johannes Berg7385.88%150.00%
Eric W. Biedermann1214.12%150.00%
Total85100.00%2100.00%

EXPORT_SYMBOL_GPL(get_net_ns_by_pid);
static __net_init int net_ns_net_init(struct net *net) { #ifdef CONFIG_NET_NS net->ns.ops = &netns_operations; #endif return ns_alloc_inum(&net->ns); }

Contributors

PersonTokensPropCommitsCommitProp
Eric W. Biedermann1954.29%125.00%
Al Viro1645.71%375.00%
Total35100.00%4100.00%


static __net_exit void net_ns_net_exit(struct net *net) { ns_free_inum(&net->ns); }

Contributors

PersonTokensPropCommitsCommitProp
Eric W. Biedermann1785.00%133.33%
Al Viro315.00%266.67%
Total20100.00%3100.00%

static struct pernet_operations __net_initdata net_ns_ops = { .init = net_ns_net_init, .exit = net_ns_net_exit, }; static const struct nla_policy rtnl_net_policy[NETNSA_MAX + 1] = { [NETNSA_NONE] = { .type = NLA_UNSPEC }, [NETNSA_NSID] = { .type = NLA_S32 }, [NETNSA_PID] = { .type = NLA_U32 }, [NETNSA_FD] = { .type = NLA_U32 }, };
static int rtnl_net_newid(struct sk_buff *skb, struct nlmsghdr *nlh) { struct net *net = sock_net(skb->sk); struct nlattr *tb[NETNSA_MAX + 1]; struct net *peer; int nsid, err; err = nlmsg_parse(nlh, sizeof(struct rtgenmsg), tb, NETNSA_MAX, rtnl_net_policy); if (err < 0) return err; if (!tb[NETNSA_NSID]) return -EINVAL; nsid = nla_get_s32(tb[NETNSA_NSID]); if (tb[NETNSA_PID]) peer = get_net_ns_by_pid(nla_get_u32(tb[NETNSA_PID])); else if (tb[NETNSA_FD]) peer = get_net_ns_by_fd(nla_get_u32(tb[NETNSA_FD])); else return -EINVAL; if (IS_ERR(peer)) return PTR_ERR(peer); spin_lock_bh(&net->nsid_lock); if (__peernet2id(net, peer) >= 0) { spin_unlock_bh(&net->nsid_lock); err = -EEXIST; goto out; } err = alloc_netid(net, peer, nsid); spin_unlock_bh(&net->nsid_lock); if (err >= 0) { rtnl_net_notifyid(net, RTM_NEWNSID, err); err = 0; } out: put_net(peer); return err; }

Contributors

PersonTokensPropCommitsCommitProp
Nicolas Dichtel18073.77%436.36%
Eric W. Biedermann3112.70%218.18%
Daniel Lezcano114.51%19.09%
Benjamin Thery104.10%19.09%
Américo Wang62.46%19.09%
Paul Moore31.23%19.09%
Stephen Hemminger31.23%19.09%
Total244100.00%11100.00%


static int rtnl_net_get_size(void) { return NLMSG_ALIGN(sizeof(struct rtgenmsg)) + nla_total_size(sizeof(s32)) /* NETNSA_NSID */ ; }

Contributors

PersonTokensPropCommitsCommitProp
Nicolas Dichtel27100.00%1100.00%
Total27100.00%1100.00%


static int rtnl_net_fill(struct sk_buff *skb, u32 portid, u32 seq, int flags, int cmd, struct net *net, int nsid) { struct nlmsghdr *nlh; struct rtgenmsg *rth; nlh = nlmsg_put(skb, portid, seq, cmd, sizeof(*rth), flags); if (!nlh) return -EMSGSIZE; rth = nlmsg_data(nlh); rth->rtgen_family = AF_UNSPEC; if (nla_put_s32(skb, NETNSA_NSID, nsid)) goto nla_put_failure; nlmsg_end(skb, nlh); return 0; nla_put_failure: nlmsg_cancel(skb, nlh); return -EMSGSIZE; }

Contributors

PersonTokensPropCommitsCommitProp
Nicolas Dichtel121100.00%3100.00%
Total121100.00%3100.00%


static int rtnl_net_getid(struct sk_buff *skb, struct nlmsghdr *nlh) { struct net *net = sock_net(skb->sk); struct nlattr *tb[NETNSA_MAX + 1]; struct sk_buff *msg; struct net *peer; int err, id; err = nlmsg_parse(nlh, sizeof(struct rtgenmsg), tb, NETNSA_MAX, rtnl_net_policy); if (err < 0) return err; if (tb[NETNSA_PID]) peer = get_net_ns_by_pid(nla_get_u32(tb[NETNSA_PID])); else if (tb[NETNSA_FD]) peer = get_net_ns_by_fd(nla_get_u32(tb[NETNSA_FD])); else return -EINVAL; if (IS_ERR(peer)) return PTR_ERR(