如何从单个流程实例创建多个网络命名空间

时间:2012-05-24 03:40:59

标签: c linux networking network-programming linux-namespaces

我正在使用以下C函数从单个流程实例创建多个网络命名空间

void create_namespace(const char *ns_name)
{
    char ns_path[100];

    snprintf(ns_path, 100, "%s/%s", "/var/run/netns", ns_name);
    close(open(ns_path, O_RDONLY|O_CREAT|O_EXCL, 0));
    unshare(CLONE_NEWNET);
    mount("/proc/self/ns/net", ns_path, "none", MS_BIND , NULL);
}

在我的进程创建所有namspaces之后,我将 tap 接口添加到任何一个网络命名空间(使用ip link set tap1 netns ns1命令),然后我实际上在所有的命名空间(可能,这实际上是一个名称不同的单个命名空间)。

但是,如果我通过使用多个进程创建多个名称空间,那么一切正常。

这里有什么问题?我是否必须将任何其他标志传递给unshare()才能从单个流程实例中获取此标志?是否存在单个流程实例无法创建多个网络命名空间的限制?或mount()调用是否存在问题,因为/proc/self/ns/net实际上已多次挂载?

更新 似乎unshare()函数正确地创建了多个网络命名空间,但/var/run/netns/中的所有挂载点实际上都引用了该挂载中安装的第一个网络命名空间。

UPDATE2: 似乎最好的方法是fork()另一个进程并从那里执行create_namespace()函数。无论如何,我很高兴听到一个更好的解决方案,它不涉及fork()调用,或者至少得到一个确认,证明不可能从单个进程创建和管理多个网络命名空间。

UPDATE3: 我可以使用以下代码使用unshare()创建多个名称空间:

int  main() {
    create_namespace("a");
    system("ip tuntap add mode tap tapa");
    system("ifconfig -a");//shows lo and tapA interface
    create_namespace("b");
    system("ip tuntap add mode tap tapb");
    system("ifconfig -a");//show lo and tapB interface, but does not show tapA. So this is second namespace created.
}

但是在进程终止并执行ip netns exec a ifconfig -aip netns exec b ifconfig -a之后,似乎两个命令都突然在命名空间 a 中执行。所以实际的问题是存储对命名空间的引用(或以正确的方式调用mount()。但我不确定,如果可行的话)。

2 个答案:

答案 0 :(得分:18)

Network Namespacesby design,是通过调用克隆创建的,可以通过取消共享进行修改。请注意,即使您使用 unshare 创建新的网络命名空间,实际上您只需修改正在运行的进程的网络堆栈。 取消共享无法修改其他进程的网络堆栈,因此您只能使用取消共享创建另一个进程。

为了工作,新的网络命名空间需要一个新的网络堆栈,因此它需要一个新的进程。就是这样。

好消息是它可以通过克隆see非常轻量级:

  

克隆()与UNIX中传统的 fork()系统调用不同   它允许父子进程有选择地共享或   重复资源。

您只能转移此网络堆栈(并避免内存空间,文件描述符表和信号处理程序表)。您的新网络流程可以更像是线程而不是真正的 fork

您可以使用C代码或Linux Kernel和/或LXC工具来操作它们。

例如,要将设备添加到新的网络命名空间,它就像:

一样简单
echo $PID > /sys/class/net/ethX/new_ns_pid

有关可用CLI的更多信息,请参阅this page

在C端,可以看看lxc-unshare实现。尽管名称使用 clone ,但can see(lxc_clone为here)。还可以查看作者选择直接使用fork的LTP implementation

编辑:有一个技巧可以让它们持久化,但你仍然需要分叉,甚至是暂时的。

看一下ipsource2的代码(为了清楚起见,我删除了错误检查):

snprintf(netns_path, sizeof(netns_path), "%s/%s", NETNS_RUN_DIR, name);

/* Create the base netns directory if it doesn't exist */
mkdir(NETNS_RUN_DIR, S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH);

/* Create the filesystem state */
fd = open(netns_path, O_RDONLY|O_CREAT|O_EXCL, 0);
[...]
close(fd);
unshare(CLONE_NEWNET);
/* Bind the netns last so I can watch for it */
mount("/proc/self/ns/net", netns_path, "none", MS_BIND, NULL)

如果在分叉进程中执行此代码,您将能够随意创建新的网络命名空间。要删除它们,您只需卸载并删除此绑定:

umount2(netns_path, MNT_DETACH);
if (unlink(netns_path) < 0) [...]

EDIT2:另一个(脏)技巧就是用系统执行“ip netns add ..”cli。

答案 1 :(得分:11)

如果您需要从另一个进程访问这些命名空间,或者需要获得能够在两者之间来回切换的句柄,您只需要绑定mount /proc/*/ns/*。不需要在单个进程中使用多个名称空间。

  • unshare 创建新的命名空间。
  • 默认情况下,clone和fork不会创建任何新的命名空间。
  • 分配给进程的每种类型的“当前”命名空间。它可以通过unshare或setns来更改。一组命名空间(默认情况下)由子进程继承。

无论何时打开(/proc/N/ns/net),它都会为此文件创建inode, 并且所有后续的open()都将返回绑定到的文件 相同的命名空细节在内核dentry缓存的深处丢失。

此外,每个进程只有一个/proc/self/ns/net文件条目,和 bind mount不会创建此proc文件的新实例。 打开这些已安装的文件与打开时完全相同 直接/proc/self/ns/net文件(将继续指向 它首次打开时指向的命名空间。)

似乎“/proc/*/ns”就像这样半生不熟。

因此,如果您只需要2个名称空间,则可以:

  • 打开/proc/1/ns/net
  • 取消共享
  • 打开/proc/self/ns/net

并在两者之间切换。

如果需要更多2,您可能需要clone()。似乎没有办法为每个进程创建多个/proc/N/ns/net文件。

但是,如果您不需要在运行时在名称空间之间切换,或者与其他进程共享它们,则可以使用许多名称空间,如下所示:

  • 打开套接字并运行主命名空间的进程。
  • 取消共享
  • 打开套接字并运行第二个命名空间(netlink,tcp等)的进程
  • 取消共享
  • ...
  • 取消共享
  • 打开套接字并运行第N个命名空间(netlink,tcp等)
  • 的进程

开放套接字保持对其网络名称空间的引用,因此在套接字关闭之前不会收集它们。

您还可以使用netlink在命名空间之间移动接口,方法是在源命名空间上发送netlink命令,并通过PID或命名空间FD(稍后您没有)指定dst命名空间。

在访问依赖于该命名空间的/proc条目之前,您需要切换进程命名空间。一旦“proc”文件打开,它就会继续引用命名空间。