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

Han*_*olo 17 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);
}
Run Code Online (Sandbox Code Playgroud)

在我的进程创建了所有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.
}
Run Code Online (Sandbox Code Playgroud)

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

Cor*_*ren 18

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

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

好消息是它可以通过克隆制作得非常轻巧,请参阅:

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

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

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

例如,要将设备添加到新的网络命名空间,它就像下面这样简单:

echo $PID > /sys/class/net/ethX/new_ns_pid
Run Code Online (Sandbox Code Playgroud)

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

在C端,可以看一下lxc-unshare实现.尽管它的名字使用了clone,你可以看到(lxc_clone就在这里).人们还可以看看LTP实现,作者选择直接使用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)
Run Code Online (Sandbox Code Playgroud)

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

umount2(netns_path, MNT_DETACH);
if (unlink(netns_path) < 0) [...]
Run Code Online (Sandbox Code Playgroud)

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


小智 11

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

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

无论何时执行open(/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"文件打开,它就会继续引用命名空间.