Как создать несколько пространств имен в сети из одного экземпляра процесса
Я использую следующую функцию 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);
}
После того, как мой процесс создает все пространства имен, и я добавляю интерфейс крана в любое из одного сетевого пространства имен (с помощью команды ip link set tap1 netns ns1
), тогда я действительно вижу этот интерфейс во всех пространствах имен (предположительно, это на самом деле один пространство имен, которое под разными именами).
Но, если я создаю несколько пространств имен, используя несколько процессов, все работает отлично.
Что здесь может быть неправильным? Должен ли я передавать какие-либо дополнительные флаги в unshare()
, чтобы заставить это работать из одного экземпляра процесса? Есть ли ограничение на то, что один экземпляр процесса не может создавать несколько пространств имен в сети? Или существует проблема с вызовом mount()
, потому что /proc/self/ns/net
фактически установлен несколько раз?
Update:
Кажется, что функция 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 -a
и ip netns exec b ifconfig -a
кажется, что обе команды были внезапно выполнены в пространстве имен a. Поэтому актуальной проблемой является сохранение ссылок на пространства имен (или правильное обращение к mount(). Но я не уверен, если это возможно).
Ответы
Ответ 1
Вам нужно только привязать mount /proc/*/ns/*
, если вам нужно получить доступ к этим пространствам имен из другого процесса или вам нужно получить дескриптор, чтобы иметь возможность переключаться между ними. Нет необходимости использовать несколько пространств имен из одного процесса.
- unshare создает новое пространство имен.
- клон и 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 в пространстве имен источников и определения пространства имен dst либо с помощью PID, либо с помощью пространства имен FD (чем позже вы этого не сделаете).
Вам нужно переключить пространство имен процессов перед доступом к /proc
записям, которые зависят от этого пространства имен. Когда файл "proc" открыт, он ссылается на пространство имен.
Ответ 2
Пространства сетевых имен, по дизайну, созданный с вызовом clone, и его можно изменить после unshare. Обратите внимание, что даже если вы создадите новое сетевое пространство имен с unshare, на самом деле вы просто модифицируете сетевой стек вашего текущего процесса. unshare не может изменить сетевой стек других процессов, поэтому вы не сможете создать еще один с помощью unshare.
Для работы новое пространство имен в сети нуждается в новом сетевом стеке, поэтому ему нужен новый процесс. Это все.
Хорошей новостью является то, что ее можно сделать очень легким с клоном, см.:
Clone() отличается от традиционного системного вызова fork() в UNIX, в что он позволяет родительскому и дочернему процессам избирательно делиться или дублировать ресурсы.
Вы можете переадресовывать только этот сетевой стек (и избегать пространства памяти, таблицы файловых дескрипторов и таблицы обработчиков сигналов). Ваш новый сетевой процесс можно сделать скорее потоком, чем реальным fork.
Вы можете манипулировать ими с помощью кода C или с помощью инструментов ядра Linux и/или LXC.
Например, чтобы добавить устройство в новое пространство имен в сети, оно просто:
echo $PID > /sys/class/net/ethX/new_ns_pid
См. эту страницу для получения дополнительной информации о доступных CLI.
На стороне C можно взглянуть на реализацию lxc-unshare. Несмотря на свое имя, он использует клон, поскольку вы можете видеть (здесь lxc_clone ). Можно также взглянуть на LTP-реализацию, где автор решил напрямую использовать fork.
РЕДАКТИРОВАТЬ. Существует трюк, который вы можете использовать, чтобы сделать их постоянными, но вам все равно придется использовать 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: Другим (грязным) трюком было бы просто выполнить команду "ip netns add.." cli с системой.