20

我正在使用以下 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);
}

在我的进程创建了所有命名空间并将一个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()。但我不确定这是否可能)。

4

2 回答 2

20

根据设计,网络命名空间是通过调用clone创建的,并且可以在unshare之后对其进行修改。请注意,即使您确实使用unshare创建了一个新的网络命名空间,实际上您也只是修改了正在运行的进程的网络堆栈。unshare无法修改其他进程的网络堆栈,因此您将无法仅使用unshare创建另一个。

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

好消息是它可以使用clone变得非常轻量级,请参阅

Clone()与 UNIX 中传统的fork()系统调用不同,它允许父进程和子进程选择性地共享或复制资源。

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

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

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

echo $PID > /sys/class/net/ethX/new_ns_pid

有关可用 CLI 的更多信息,请参阅此页面。

在 C 端,可以查看 lxc-unshare 实现。如您所见(lxc_clone is here) ,尽管它的名字使用了clone 。也可以看LTP implementation,作者选择直接使用fork。

编辑:您可以使用一个技巧使它们持久化,但您仍然需要分叉,即使是暂时的。

看看这段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:另一个(肮脏的)技巧是简单地使用system执行“ip netns add ..”cli 。

于 2012-05-30T09:50:27.877 回答
13

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

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

每当您执行 open( /proc/N/ns/net) 时,它都会为该文件创建 inode,并且所有后续 open() 将返回绑定到同一名称空间的文件。细节在内核目录缓存的深处丢失。

此外,每个进程只有一个/proc/self/ns/net文件条目,并且绑定挂载不会创建此 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”文件打开,它就会保持对命名空间的引用。

于 2012-06-04T13:10:29.120 回答