网络命名空间(内核源码实现)
13.1 命名空间创建
在当前Linux下,对进程而言一个命名空间包括五个具体的命名空间,mnt、uts、ipc、pid以及net,在os启动时,其会初始化一个称为init的初始命名空间,并将该命名空间给创建的进程,在后续创建进程时,将根据创建的flag确定是否创建新的命名空间。在图13.1.1中,进程1、2都指向了init命名空间,在创建进程3时,明确指定了复制网络命名空间,其它的命名空间依然会继承(copy)。
图13.1.1 命名空间和进程的组合
创建命名空间的系统调用如下,nstype是创建进程时指定的创建命名空间的标志。
kernel/nsproxy.c
- 239 SYSCALL_DEFINE2(setns, int, fd, int, nstype)
- 240 {
- 241 const struct proc_ns_operations *ops;
- 242 struct task_struct *tsk = current; 获得当前进程描述结构体
- 243 struct nsproxy *new_nsproxy;
- // 创建一个新的命名空间的调用
- 258 new_nsproxy = create_new_namespaces(0, tsk, current_user_ns(), tsk->fs);
- //proc目录下信息注册
- 264 err = ops->install(new_nsproxy, ei->ns);
- //将新创建的命名空间new_nsproxy赋值给当前进程,即替换掉以前的命名空间。
- 269 switch_task_namespaces(tsk, new_nsproxy);
- 273 }
258调用的函数依然在nsproxy.c文件,该函数定义见59行。
- 59 static struct nsproxy *create_new_namespaces(unsigned long flags,
- 60 struct task_struct *tsk, struct user_namespace *user_ns,
- 61 struct fs_struct *new_fs)
- 62 {
- 63 struct nsproxy *new_nsp;
- 64 int err;
- //从nsproxy_cachep中申请一个nsproxy缓存,并将引用计数置1,
- 66 new_nsp = create_nsproxy();
- /mnt命名空间
- 70 new_nsp->mnt_ns = copy_mnt_ns(flags, tsk->nsproxy->mnt_ns, user_ns, new_fs);
- //uts命名空间
- 76 new_nsp->uts_ns = copy_utsname(flags, user_ns, tsk->nsproxy->uts_ns);
- //ipc命名空间
- 82 new_nsp->ipc_ns = copy_ipcs(flags, user_ns, tsk->nsproxy->ipc_ns);
- //ns命名空间
- 88 new_nsp->pid_ns = copy_pid_ns(flags, user_ns, tsk->nsproxy->pid_ns);
- //网络命名空间
- 94 new_nsp->net_ns = copy_net_ns(flags, user_ns, tsk->nsproxy->net_ns);
- 117 }
类似图13.1.1中进程3那样需要部分复制命名空间时(clone系统调用),copy_namespaces()将被调用。
- 123 int copy_namespaces(unsigned long flags, struct task_struct *tsk)
- 124 {
- 125 struct nsproxy *old_ns = tsk->nsproxy; //当前进程命名空间
- 126 struct user_namespace *user_ns = task_cred_xxx(tsk, user_ns);
- 127 struct nsproxy *new_ns;
- 128 int err = 0;
- 129
- 130 if (!old_ns)
- 131 return 0;
- //引用计数原子加1
- 133 get_nsproxy(old_ns);
- //复制标志
- 135 if (!(flags & (CLONE_NEWNS | CLONE_NEWUTS | CLONE_NEWIPC |
- 136 CLONE_NEWPID | CLONE_NEWNET)))
- 137 return 0;
- //权能
- 139 if (!ns_capable(user_ns, CAP_SYS_ADMIN)) {
- 140 err = -EPERM;
- 141 goto out;
- 142 }
- 143
- //在复制ipc空间时,存在不同命名空间信号量的问题,切换到新的命名空间意味着原来的信号量将不可达,undolist(未处理
- //信号的链表必须和新的ipc空间分离),如果标志设置有CLONE_SYSVSEM,意味着要求信号量共享,那就意味着不能创
- //建新的ipc空间了。
- 151 if ((flags & CLONE_NEWIPC) && (flags & CLONE_SYSVSEM)) {
- 152 err = -EINVAL;
- 153 goto out;
- 154 }
- //创建命名空间
- 156 new_ns = create_new_namespaces(flags, tsk, user_ns, tsk->fs);
- 157 if (IS_ERR(new_ns)) {
- 158 err = PTR_ERR(new_ns);
- 159 goto out;
- 160 }
- //将进程的命名空间空间指针指向新的命名空间
- 162 tsk->nsproxy = new_ns;
- 163
- 164 out:
- 165 put_nsproxy(old_ns);
- 166 return err;
- 167 }
13.2 网络命名空间管理
网络命名空间操作主要在net/core/net_namespace.c,接13.1节的copy_net_ns()函数。该函数返回值是一个网络空间。创建网络命名空间时以创建网络命名空间进程的nsproxy-> net_ns为网络命名空间的原型,这是因为不同的网络命名空间很多的基础设施是一样的,比如对loopback回环接口的支持、TCP的支持、UDP支持、IP支持等等。
- 238 struct net *copy_net_ns(unsigned long flags,
- 239 struct user_namespace *user_ns, struct net *old_net)
- 240 {
- 241 struct net *net;
- 242 int rv;
- //如果flag中CLONE_NEWNET没有设置,说明不需要创建新的网络命名空间,直接返回原来的命名空间。否则创建新的网
- //络命名空间
- 244 if (!(flags & CLONE_NEWNET))
- 245 return get_net(old_net);
- //从net_cachep为新的网络命名空间申请内存。
- 247 net = net_alloc();
- //增加user命名空间的引用计数
- 251 get_user_ns(user_ns);
- 252
- 253 mutex_lock(&net_mutex);
- //调用pernet_list上注册的服务函数,对这个新的网络命名空间执行init。
- 254 rv = setup_net(net, user_ns);
- 255 if (rv == 0) {
- 256 rtnl_lock();
- 257 list_add_tail_rcu(&net->list, &net_namespace_list); // net_namespace_list为所有命名空间串接的链表。
- 258 rtnl_unlock();
- 259 }
- 260 mutex_unlock(&net_mutex);
- 261 if (rv < 0) { //出错处理
- //user命名空间计数值减一
- 262 put_user_ns(user_ns);
- //释放net命名空间。
- 263 net_drop_ns(net);
- 264 return ERR_PTR(rv);
- 265 }
- 266 return net;
- 267 }
254行的setup_net()如下, 其主要工作就是遍历pernet_list获得对应的structpernet_operations结构体,然后将struct pernet_operations结构对应的实例的init成员函数作用于新创建的命名空间。
- 150 static __net_init int setup_net(struct net *net, struct user_namespace *user_ns)
- 151 {
- 166 list_for_each_entry(ops, &pernet_list, list) {
- 167 error = ops_init(ops, net);
- 168 if (error < 0)
- 169 goto out_undo;
- 170 }
- 189 }
structpernet_operations的实例还是很多的,以下截图展示了其中的一部分,对于arp.c文件,其arp_net_ops的arp_net_init()实例会在167行被调用,以初始化新网络命名空间arp协议在proc目录的接口。
图13.2.1 structpernet_operations实例
其它网络命名空间的函数如下:
get_net_ns_by_fd()根据文件描述获得网络命名空间
get_net_ns_by_pid()根据进程ID获得网络命名空间。
net_ns_init()网络命名空间初始化,pure_initcall声明,在系统启动时会被调用初始化网络命名空间。
register_pernet_subsys()注册图13.2.1中的各种操作集,调用register_pernet_operations(),完成,实质工作在__register_pernet_operations()函数中完成真正的注册。
unregister_pernet_subsys注销