【问题标题】:How to create multiple network namespace from a single process instance如何从单个流程实例创建多个网络命名空间
【发布时间】:2012-05-30 15:28:02
【问题描述】:

我正在使用以下 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/ 中的所有挂载点实际上都引用了该目录中挂载的第一个网络命名空间。

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

更新3: 我可以使用以下代码使用 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()。但我不确定这是否可能)。

【问题讨论】:

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


    【解决方案1】:

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

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

    好消息是它可以通过clone变得非常轻量级,see:

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

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

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

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

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

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

    在 C 端,可以查看 lxc-unshare 实现。尽管它的名字它使用clone,就像你can see(lxc_clone 是here)。也可以看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)
    

    如果您在分叉的进程中执行此代码,您将能够随意创建新的网络命名空间。为了删除它们,您可以简单地 umount 并删除此绑定:

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

    EDIT2:另一个(肮脏的)技巧是简单地使用system执行“ip netns add ..”cli。

    【讨论】:

    • +1,但您能解释一下“请注意,您不会使用 unshare 创建新的网络命名空间”是什么意思吗?请参阅更新 #3,因为我的理解是 unshare() 仍然可以创建网络名称空间。 clone(CLONE_NEWNET) 类似于“我将使用新的网络命名空间创建一个新的子进程”,而 unshare(CLONE_NEWNET) 类似于“我不想再与我的父进程共享网络命名空间。所以创建一个新的一个。”。 lxc 使用 clone(),而 iproute2 使用 unshare()。
    • 我会尽力解释。你用 unshare 为你的 current 进程创建了一个网络命名空间,但是由于网络命名空间需要一个 PID 才能存在,你不能只用 unshare 为 same 进程。
    • 我明白你的意思,但 unshare() 仍然可以创建一个新的网络命名空间(这需要更新你的答案)。另外,我猜,命名空间不一定需要一个实际的 PID(例如,在执行“ip netns add nsX”命令后,ip 进程终止,但命名空间 nsX 仍然存在)。我猜这个限制“为什么不可能从单个进程创建多个网络命名空间”必须与 mount() 的工作方式有关。
    • 如果您查看iproute2 source code,您会发现即使在进程因挂载技巧而死后,它们也会保留当前的网络堆栈:/* Bind the netns last so I can watch for it */ if (mount("/proc/self/ns/net", netns_path, "none", MS_BIND, NULL) &lt; 0)
    • 他们使用 linux 内核的这个美妙的特性来持久化他们的网络命名空间,因为它们仍然被正在运行的进程打开。
    【解决方案2】:

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

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

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

    此外,每个进程只有一个/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”文件打开,它就会保持对命名空间的引用。

    【讨论】:

      猜你喜欢
      • 2011-07-05
      • 1970-01-01
      • 2021-12-06
      • 2015-04-17
      • 2017-08-05
      • 2019-10-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多