Linux命名空间学习教程(五)NET


【编者的话】Docker核心解决的问题是利用LXC来实现类似VM的功能,从而利用更加节省的硬件资源提供给用户更多的计算资源。而 LXC所实现的隔离性主要是来自内核的命名空间, 其中pid、net、ipc、mnt、uts 等命名空间将容器的进程、网络、消息、文件系统和hostname 隔离开。本文是Linux命名空间系列教程的第五篇,重点介绍NET命名空间。DockerOne在撸代码的基础上进行了校对和整理。

阅读完上一篇关于NS namespace的文章(挂载点表的隔离),你想更深入接近一个全功能的VM吗?好!这个系列的上两篇文章都在试图带您了解VM,而使用“NET” namespace隔离网络接口(这是真的,这不是梦)和user/group描述符可以让VM更加透明。如果你尚未阅读过之前的文章,我强烈建议你先阅读一遍这个系列的第一篇文章,了解下Linux namespace隔离机制。

这一次我们先不讲添加额外的“CLONE_NEWNET”标志到“clone”系统调用。这个留着之后说明。现在,恕我直言,这个namespace最好的开始方式是异常强大的“iproute2”——网管的瑞士军刀。如果你到现在都还没有这个工具,我强烈建议你安装一个。如果你不想这么做,你不妨跳过解释部分,直接进入完整的代码示例。

首先,让我们看看现在有哪些网络接口。
ip link list

它会像这样输出:
1: lo: <LOOPBACK,UP,LOWER_UP> 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: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc pfifo_fast state DOWN mode DEFAULT qlen 1000
link/ether **:**:**:**:**:** brd ff:ff:ff:ff:ff:ff
3: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP mode DORMANT qlen 1000
link/ether **:**:**:**:**:** brd ff:ff:ff:ff:ff:ff

...



这里没有什么意料之外的事情。我有一个工作着的loopback,UP(是的,‘UNKNOWN’表示‘UP’),我也连接着我的无线网络,当然这里还有其他的连接,只不过为了这篇文章被隐藏了。

现在,我们创建一个网络namespace,并从内部运行同样的命令:

create a network namespace called "demo"

ip netns add demo

exec "ip link list" inside the namespace

ip netns exec demo ip link list

输出如下:
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT 
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00

嗯,这里有一个loopback,它的状态为“DOWN”。更有趣的是,它是完全和主loopback隔离的。也就是说,任何在namespace内和这个loopback绑定的应用,只能够和同样在这个namespace内的软件通信。准确的说,是和IPC namespce同样的隔离级别。是不是很high?

对的,但是我该如何与interwebz通讯呢?

这里有几种解决方案。最简单最普通的一种是,在“Host”与“Guest”之间,创建一个点对点(Point-to-Point)管道。Linux Kernel再一次提供了多种替代品。我建议使用“veth”接口,因为它和生态系统整合得最好,尤其是iproute2。,因为它被LXC使用,而事实上它来自OpenVZ项目,经过很严格的测试获得代码。另一个可选方案是“etun”驱动。它在概念上和另外一个名字相同,但是不知道有任何使用了它。

“veth”和“etun”都创建一对虚拟的接口和当前namespace中的另一个连接。你可以选择一个,并在目标namespace中移动它,以此得到一个通讯频道(channel)。为了便于理解,你可以将其想象成复杂的粒子运动。

下一步,给它们一个IP,设置为up,然后ping!如下是一个bash会话(session),能够完成这件事:

Create a "demo" namespace

ip netns add demo

create a "veth" pair

ip link add veth0 type veth peer name veth1

and move one to the namespace

ip link set veth1 netns demo

configure the interfaces (up + IP)

ip netns exec demo ip link set lo up
ip netns exec demo ip link set veth1 up
ip netns exec demo ip addr add 169.254.1.2/30 dev veth1
ip link set veth0 up
ip addr add 169.254.1.1/30 dev veth0

测试一下!没什么吓人的。

如果你需要使用“veth”技术从“guest”系统联网,你可以设置伪装地址——以“NAT”为人们所知。同样的,为了让一个在namespace监听80端口的webserver,直接在主接口上监听,可以使用“DNAT”——以端口转发为人们所知。我将这个留给读者思考。

这里是一个基本的例子,让我们快速进入:

make sure ip forwarding is enabled

echo 1 > /proc/sys/net/ipv4/ip_forward

enable Internet access for the namespace, assuming you ran the previous example

iptables -t nat -A POSTROUTING -i veth0 -j  MASQUERADE

Forward main ":80" to guest ":80"

iptables -t nat -A PREROUTING -d <your main ip>/32 -p tcp --dport 80 -j  DNAT --to-destination  169.254.1.2:80

现在,我们将它们放置在一起,并且将CLONE_NEWNET标记添加到clone系统调用。为了简化,我们通过system()直接调用“ip”。
main-5-net.c

define _GNU_SOURCE

include <sys/types.h>

include <sys/wait.h>

include <sys/mount.h>

include <stdio.h>

include <sched.h>

include <signal.h>

include <unistd.h>

include <stdlib.h>

define STACK_SIZE (1024 * 1024)

// sync primitive
int checkpoint[2];
static char child_stack[STACK_SIZE];
char* const child_args[] = {
"/bin/bash",
NULL
};

int child_main(void* arg) {
char c;
// init sync primitive
close(checkpoint[1]);
// setup hostname
printf(" - [%5d] World !\n", getpid());
sethostname("In Namespace", 12);
// remount "/proc" to get accurate "top" && "ps" output
mount("proc", "/proc", "proc", 0, NULL);
// wait for network setup in parent
read(checkpoint[0], &c, 1);
// setup network
system("ip link set lo up");
system("ip link set veth1 up");
system("ip addr add 169.254.1.2/30 dev veth1");
execv(child_args[0], child_args);
printf("Ooops\n");
return 1;
}

int main() {
// init sync primitive
pipe(checkpoint);
printf(" - [%5d] Hello ?\n", getpid());
int child_pid = clone(child_main, child_stack+STACK_SIZE,
  CLONE_NEWUTS | CLONE_NEWIPC | CLONE_NEWPID | CLONE_NEWNS | CLONE_NEWNET | SIGCHLD, NULL);
// further init: create a veth pair
char* cmd;
asprintf(&cmd, "ip link set veth1 netns %d", child_pid);
system("ip link add veth0 type veth peer name veth1");
system(cmd);
system("ip link set veth0 up");
system("ip addr add 169.254.1.1/30 dev veth0");
free(cmd);
// signal "done"
close(checkpoint[1]);
waitpid(child_pid, NULL, 0);
return 0;
}

试着运行一下!
jean-tiare@jeantiare-Ubuntu:~/blog$ gcc -Wall main.c -o ns && sudo ./ns
- [22094] Hello ?
- [    1] World !
root@In Namespace:~/blog$ # run a super-powerful server, fully isolated
root@In Namespace:~/blog$ nc -l 4242
Hi !
Bye...
root@In Namespace:~/blog$ exit
jean-tiare@jeantiare-Ubuntu:~/blog$ # done !

如果在另外一个终端按照如下程序执行,我们就能看到上述结果。
jean-tiare@jeantiare-Ubuntu:~$ nc 169.254.1.2 4242
Hi !    
Bye...
jean-tiare@jeantiare-Ubuntu:~$ 

为了能够在网络虚拟化上走得更远,你可以了解下Linux内核最近引入的接口类型:macvlan,vlan,vxlans,...

这就是“NET” namespace的全部。它是如此的强大,以至于可以作为“CORE”轻量级网络模拟器的基础。下一篇文章,我们将探索最后一个并且是最复杂的namespace “USER”。谢谢阅读!

原文链接:Introduction to Linux namespaces – Part 5: NET(翻译:孙科 审校:李颖杰)

-----------------------------------------
Linux命名空间学习教程(一) UTS
Linux命名空间学习教程(二) IPC
Linux命名空间学习教程(三) PID
Linux命名空间学习教程(四)NS(FS)
Linux命名空间学习教程(五)NET

0 个评论

要回复文章请先登录注册