本文为原文翻译,特此声明。
原文链接:https://blog.jtlebi.fr/2014/01/19/introduction-to-linux-namespaces-part-5-net/
原文中有部分内容为便于理解,并非直译,可能存在一定的疏漏,因此请参考原文理解。
继上一篇 [关于NS namespace的文章]1,你想更深入更接近一个全功能的VM吗?好!这个系列的上两篇post很明确地专注于此。而使用“NET” namespace隔离网络接口(这是真的,这不是梦)和user/group描述符让它(VM)更加透明。如果你尚未阅读过之前的post,我强烈建议你先阅读一遍这个系列的第一篇post,了解下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 -o 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”。谢谢阅读!
确实非常不错,很有收获!
补充下关于DNA那个命令有误
iptables -t nat -A POSTROUTING -o veth0 -j MASQUERADE &&
iptables -t nat -A PREROUTING -d yourip/24 -p tcp --dport 80 -j DNAT --to-destination 169.254.1.2:80
确实有问题,谢谢指正。