背景
从内核2.6.24开始,Linux支持6种不同类型的命名空间。 命名空间在创建与系统其他部分更为隔离的过程中非常有用,而无需使用完整的低级虚拟化技术。
-
CLONE_NEWIPC
:IPC命名空间:SystemV IPC和POSIX消息队列可以隔离。 -
CLONE_NEWPID
:PID命名空间:PID是隔离的,这意味着命名空间内的虚拟PID可能与命名空间之外的PID冲突。 命名空间内的PID将映射到命名空间之外的其他PID。 命名空间中的第一个PID将为“1”,在命名空间之外将其分配给init
-
CLONE_NEWNET
:网络命名空间
:网络(/ proc / net
,IP,接口和路由)是隔离的。 服务可以在命名空间中的相同端口上运行,并且可以创建“重复”虚拟接口。 -
CLONE_NEWNS
:装载命名空间。 我们有能力在进程中看到安装点。 使用挂载命名空间,我们可以实现与chroot()
类似的功能,但具有改进的安全性。 -
CLONE_NEWUTS
:UTS命名空间。 这个命名空间的主要目的是隔离主机名和NIS名称。 -
CLONE_NEWUSER
:用户名空间。 这里,用户名和组ID在命名空间内外是不同的,可以重复。
我们先来看一下C程序的结构,这个程序需要演示进程名称空间。 以下已经在Debian 6和7上进行了测试。首先,我们需要在上分配一页内存,并设置一个指向该内存页面末尾的指针。 我们使用alloca
来分配内存而不是分配内存在堆上的malloc
。
void *mem = alloca(sysconf(_SC_PAGESIZE)) + sysconf(_SC_PAGESIZE);
接下来,我们使用clone
创建一个子进程,传递我们的子栈“mem”的位置以及指定新命名空间的必需标志。 我们将'callee'指定为在子空间内执行的功能:
mypid = clone(callee, mem, SIGCHLD | CLONE_NEWIPC | CLONE_NEWPID | CLONE_NEWNS | CLONE_FILES, NULL);
在调用clone
之后,我们等待子进程完成,然后终止父进程。 如果没有,父执行流程将继续并立即终止,清除孩子:
while (waitpid(mypid, &r, 0) < 0 && errno == EINTR) { continue; }
最后,我们将返回到shell的退出代码:
if (WIFEXITED(r)) { return WEXITSTATUS(r); } return EXIT_FAILURE;
现在,我们来看看被调用
函数:
static int callee() { int ret; mount("proc", "/proc", "proc", 0, ""); setgid(u); setgroups(0, NULL); setuid(u); ret = execl("/bin/bash", "/bin/bash", NULL); return ret; }
在这里,我们挂载一个/ proc
文件系统,然后在生成/ bin / bash
shell之前将uid(User ID)和gid(Group ID)设置为'u'。 LXC是一种操作系统级虚拟化工具,利用cgroups和命名空间进行资源隔离。 让我们把它放在一起,将“u”设置为65534,这是用户“nobody”,而在Debian上是“nogroup”组。
#define _GNU_SOURCE #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/wait.h> #include <sys/mount.h> #include <grp.h> #include <alloca.h> #include <errno.h> #include <sched.h> static int callee(); const int u = 65534; int main(int argc, char *argv[]) { int r; pid_t mypid; void *mem = alloca(sysconf(_SC_PAGESIZE)) + sysconf(_SC_PAGESIZE); mypid = clone(callee, mem, SIGCHLD | CLONE_NEWIPC | CLONE_NEWPID | CLONE_NEWNS | CLONE_FILES, NULL); while (waitpid(mypid, &r, 0) < 0 && errno == EINTR) { continue; } if (WIFEXITED(r)) { return WEXITSTATUS(r); } return EXIT_FAILURE; } static int callee() { int ret; mount("proc", "/proc", "proc", 0, ""); setgid(u); setgroups(0, NULL); setuid(u); ret = execl("/bin/bash", "/bin/bash", NULL); return ret; }
执行代码产生以下内容:
root@w:~/pen/tmp# gcc -O -o ns.c -Wall -Werror -ansi -c89 ns.c root@w:~/pen/tmp# ./ns nobody@w:~/pen/tmp$ id uid=65534(nobody) gid=65534(nogroup) nobody@w:~/pen/tmp$ ps auxw USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND nobody 1 0.0 0.0 4620 1816 pts/1 S 21:21 0:00 /bin/bash nobody 5 0.0 0.0 2784 1064 pts/1 R+ 21:21 0:00 ps auxw nobody@w:~/pen/tmp$
请注意,UID和GID设置为nobody和nogroup。 特别注意,完整的ps输出仅显示两个正在运行的进程,它们的PID分别为1和5。 现在,我们继续使用ip netns
来处理网络命名空间。 首先,我们确认目前没有命名空间:
root@w:~# ip netns list Object "netns" is unknown, try "ip help".
在这种情况下,任一ip
需要一个升级,或内核。 假设你的内核比2.6.24更新,最有可能是ip
。 升级后, ip netns列表
默认情况下不返回任何内容。 我们添加一个名为'ns1'的新命名空间:
root@w:~# ip netns add ns1 root@w:~# ip netns list ns1
首先,列出当前界面:
root@w:~# ip link list 1: lo: mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 2: eth0: mtu 1500 qdisc pfifo_fast state UNKNOWN mode DEFAULT qlen 1000 link/ether 00:0c:29:65:25:9e brd ff:ff:ff:ff:ff:ff
现在要创建一个新的虚拟接口,并将其添加到我们的新命名空间。 虚拟接口成对创建,并且彼此链接 - 想象一个虚拟交叉电缆:
root@w:~# ip link add veth0 type veth peer name veth1 root@w:~# ip link list 1: lo: mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 2: eth0: mtu 1500 qdisc pfifo_fast state UNKNOWN mode DEFAULT qlen 1000 link/ether 00:0c:29:65:25:9e brd ff:ff:ff:ff:ff:ff 3: veth1: mtu 1500 qdisc noop state DOWN mode DEFAULT qlen 1000 link/ether d2:e9:52:18:19:ab brd ff:ff:ff:ff:ff:ff 4: veth0: mtu 1500 qdisc noop state DOWN mode DEFAULT qlen 1000 link/ether f2:f7:5e:e2:22:ac brd ff:ff:ff:ff:ff:ff
ifconfig -a
还将显示添加veth0和veth1。
很好,现在要将我们的新界面分配给命名空间。 请注意, ip netns exec
用于在命名空间中执行命令:
root@w:~# ip link set veth1 netns ns1 root@w:~# ip netns exec ns1 ip link list 1: lo: mtu 65536 qdisc noop state DOWN mode DEFAULT link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 3: veth1: mtu 1500 qdisc noop state DOWN mode DEFAULT qlen 1000 link/ether d2:e9:52:18:19:ab brd ff:ff:ff:ff:ff:ff
ifconfig -a
现在只显示veth0,因为veth1在ns1命名空间中。
我们是否要删除veth0 / veth1:
ip netns exec ns1 ip link del veth1
我们现在可以在我们的主机上将IP地址为192.168.5.5/24分配给veth0:
ifconfig veth0 192.168.5.5/24
并在ns1中分配veth1 192.168.5.10/24:
ip netns exec ns1 ifconfig veth1 192.168.5.10/24 up
在我们的主机和我们的命名空间中
执行ip addr列表
:
root@w:~# ip addr list 1: lo: mtu 65536 qdisc noqueue state UNKNOWN link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo inet6 ::1/128 scope host valid_lft forever preferred_lft forever 2: eth0: mtu 1500 qdisc pfifo_fast state UNKNOWN qlen 1000 link/ether 00:0c:29:65:25:9e brd ff:ff:ff:ff:ff:ff inet 192.168.3.122/24 brd 192.168.3.255 scope global eth0 inet6 fe80::20c:29ff:fe65:259e/64 scope link valid_lft forever preferred_lft forever 6: veth0: mtu 1500 qdisc pfifo_fast state UP qlen 1000 link/ether 86:b2:c7:bd:c9:11 brd ff:ff:ff:ff:ff:ff inet 192.168.5.5/24 brd 192.168.5.255 scope global veth0 inet6 fe80::84b2:c7ff:febd:c911/64 scope link valid_lft forever preferred_lft forever root@w:~# ip netns exec ns1 ip addr list 1: lo: mtu 65536 qdisc noop state DOWN link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 5: veth1: mtu 1500 qdisc pfifo_fast state UP qlen 1000 link/ether 12:bd:b6:76:a6:eb brd ff:ff:ff:ff:ff:ff inet 192.168.5.10/24 brd 192.168.5.255 scope global veth1 inet6 fe80::10bd:b6ff:fe76:a6eb/64 scope link valid_lft forever preferred_lft forever
查看命名空间内外的路由表:
root@w:~# ip route list default via 192.168.3.1 dev eth0 proto static 192.168.3.0/24 dev eth0 proto kernel scope link src 192.168.3.122 192.168.5.0/24 dev veth0 proto kernel scope link src 192.168.5.5 root@w:~# ip netns exec ns1 ip route list 192.168.5.0/24 dev veth1 proto kernel scope link src 192.168.5.10
最后,为了连接我们的物理和虚拟接口,我们需要一个桥梁。 让我们在主机上桥接eth0和veth0,然后使用DHCP在ns1命名空间中获取一个IP:
root@w:~# brctl addbr br0 root@w:~# brctl addif br0 eth0 root@w:~# brctl addif br0 veth0 root@w:~# ifconfig eth0 0.0.0.0 root@w:~# ifconfig veth0 0.0.0.0 root@w:~# dhclient br0 root@w:~# ip addr list br0 7: br0: mtu 1500 qdisc noqueue state UP link/ether 00:0c:29:65:25:9e brd ff:ff:ff:ff:ff:ff inet 192.168.3.122/24 brd 192.168.3.255 scope global br0 inet6 fe80::20c:29ff:fe65:259e/64 scope link valid_lft forever preferred_lft forever
br0已分配IP为192.168.3.122/24。 现在为命名空间:
root@w:~# ip netns exec ns1 dhclient veth1 root@w:~# ip netns exec ns1 ip addr list 1: lo: mtu 65536 qdisc noop state DOWN link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 5: veth1: mtu 1500 qdisc pfifo_fast state UP qlen 1000 link/ether 12:bd:b6:76:a6:eb brd ff:ff:ff:ff:ff:ff inet 192.168.3.248/24 brd 192.168.3.255 scope global veth1 inet6 fe80::10bd:b6ff:fe76:a6eb/64 scope link valid_lft forever preferred_lft forever
优秀! veth1已分配192.168.3.248/24