Как создать несколько сетевых пространств имен из одного экземпляра процесса

Я использую следующую функцию 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: кажется, что лучший подход — разветвить() другой процесс и выполнить оттуда функцию 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 -a и ip netns exec b ifconfig -a кажется, что обе команды были внезапно выполнены в пространстве имен a. Таким образом, фактическая проблема заключается в хранении ссылок на пространства имен (или правильном вызове mount(). Но я не уверен, возможно ли это).


person user389238    schedule 24.05.2012    source источник


Ответы (2)


Вам нужно привязать mount /proc/*/ns/* только в том случае, если вам нужно получить доступ к этим пространствам имен из другого процесса или вам нужно получить дескриптор, чтобы иметь возможность переключаться между ними. Нет необходимости использовать несколько пространств имен из одного процесса.

  • unshare делает создание нового пространства имен.
  • clone и fork по умолчанию не создают новых пространств имен.
  • существует одно «текущее» пространство имен каждого типа, назначенное процессу. Его можно изменить unshare или setns. Набор пространств имен (по умолчанию) наследуется дочерними процессами.

Всякий раз, когда вы делаете open(/proc/N/ns/net), он создает индекс для этого файла, и все последующие open() будут возвращать файл, привязанный к тому же пространству имен. Детали теряются в глубинах кэша dentry ядра.

Кроме того, каждый процесс имеет только одну запись в файле /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 в исходное пространство имен и указав пространство имен dst либо с помощью PID, либо с помощью FD пространства имен (более позднего у вас нет).

Вам необходимо переключить пространство имен процесса перед доступом к /proc записям, которые зависят от этого пространства имен. Когда файл «proc» открыт, он сохраняет ссылку на пространство имен.

person juris    schedule 04.06.2012

Сетевые пространства имен предназначены, созданный с помощью вызова clone, и его можно изменить после unshare. Обратите внимание, что даже если вы создаете новое сетевое пространство имен с помощью unshare, на самом деле вы просто изменяете сетевой стек запущенного процесса. unshare не может изменять сетевой стек других процессов, поэтому вы не сможете создать еще один только с помощью unshare.

Чтобы новое сетевое пространство имен работало, ему нужен новый сетевой стек, а значит, и новый процесс. Это все.

Хорошая новость заключается в том, что его можно сделать очень легким с помощью clone, см.:

Clone() отличается от традиционного системного вызова fork() в UNIX тем, что позволяет родительскому и дочернему процессам выборочно совместно использовать или дублировать ресурсы.

Вы можете отвлекаться только на этот сетевой стек (и избегать памяти, таблицы файловых дескрипторов и таблицы обработчиков сигналов). Ваш новый сетевой процесс можно сделать больше похожим на поток, чем на настоящую ветвь.

Вы можете манипулировать ими с помощью кода C или с помощью ядра Linux и/или инструментов LXC.

Например, чтобы добавить устройство в новое сетевое пространство имен, это так же просто, как:

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

См. эту страницу для получения дополнительной информации о доступном интерфейсе командной строки.

На стороне C можно взглянуть на реализацию lxc-unshare. Несмотря на свое название, он использует клон, поскольку вы можно увидеть (lxc_netbjgitlxcref.="http://http://gitlxcref.="http://gitlxc. cgi?p=lxc/lxc;a=blob;f=src/lxc/namespace.c;h=3e6fc3aad54f842ab16d35bc3d8521c65e973d7b;hb=00ad19d4dba5c05401125d4217dc8f4e7fd9403a#l50">здесь). Также можно посмотреть Реализация LTP, где автор решил использовать форк напрямую.

РЕДАКТИРОВАТЬ: есть трюк, который вы можете использовать, чтобы сделать их постоянными, но вам все равно придется разветвляться, даже временно.

Взгляните на этот код 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 с помощью system.

person Coren    schedule 30.05.2012
comment
+1, но не могли бы вы объяснить, что вы имеете в виду под «Обратите внимание, что вы не создаете новое сетевое пространство имен с помощью unshare?» См. обновление № 3, потому что я понимаю, что unshare() все еще может создавать сетевые пространства имен. Клон (CLONE_NEWNET) — это что-то вроде того, что я собираюсь создать новый дочерний процесс с новым сетевым пространством имен, а unshare (CLONE_NEWNET) — это то, что я больше не хочу делиться сетевым пространством имен с моим родительским процессом. Поэтому создайте новый. lxc использует clone(), а iproute2 использует unshare(). - person user389238; 01.06.2012
comment
Я попытаюсь объяснить. Вы создаете сетевое пространство имен для своего текущего процесса с помощью unshare, но, поскольку сетевым пространствам имен для работы требуется PID, вы не сможете создать новое только с помощью < i>unshare для того же процесса. - person Coren; 02.06.2012
comment
Я вижу, вы указываете, но unshare() все еще может создать новое сетевое пространство имен (для этого требуется обновление вашего ответа). Кроме того, я предполагаю, что пространству имен не обязательно нужен фактический PID, в котором он будет жить (например, после выполнения команды ip netns add nsX процесс ip завершается, но пространство имен nsX все еще остается). Я предполагаю, что это ограничение, из-за которого невозможно создать несколько сетевых пространств имен из одного процесса, должно что-то делать с тем, как работает mount(). - person user389238; 02.06.2012
comment
Если вы посмотрите на исходный код iproute2, вы увидите, что они сохраняют текущий сетевой стек даже после того, как процесс умер с помощью трюка с монтированием: /* Bind the netns last so I can watch for it */ if (mount("/proc/self/ns/net", netns_path, "none", MS_BIND, NULL) < 0) - person Coren; 02.06.2012
comment
Они делают свое сетевое пространство имен постоянным, используя эту замечательную функцию ядра Linux об удаленных, но все еще пригодных для использования файлах, поскольку они все еще открываются запущенным процессом. - person Coren; 02.06.2012