且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

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

更新时间:2022-09-14 18:45:11

本文讲的是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(翻译:孙科 审校:李颖杰)

原文发布时间为: 2014-12-25
本文作者:codesun
本文来自云栖社区合作伙伴DockerOne,了解相关信息可以关注DockerOne。
原文标题:Linux命名空间学习教程(五)NET