Дескрипторы файлов процесса можно увидеть в каталоге /proc/pid/fd, где некоторые из них можно открыть, прочитать или записать. Например, если я перенаправляю что-то в процесс через стандартный ввод, это будет отображаться как /proc/pid/fd/0: «0 -> 'pipe:[19918]'|» (лс -лФ); Затем я могу прочитать их из другого процесса, при этом данные иногда передаются в один процесс, а что-то — в другой.
Однако если дескриптор файла является сокетом, это не работает. Он отображается как /proc/pid/fd/3: "3 -> 'socket:[8577]'=", но его открытие приводит к ошибке «Нет такого устройства или адреса».
В первом сценарии fd выглядит как именованный канал, но во втором он не становится именованным сокетом. Теперь, хотя есть именованные сокеты, они создаются привязкой и к ним необходимо подключиться. Сокет выше будет результатом принятия или сокета/подключения, и неясно, как к нему подключиться.
Таким образом, открытие не работает, а подключение не может работать, потому что fd не прослушивается. Есть ли что-то, что сработает? В моих примерах оба типа fd показывают номер в листинге ls — 19918 для канала и 8577 для сокета, что указывает на то, что существует определенная конечная точка ядра для подключения (?), но я не знаю, какой API может быть привык к нему прикрепляться. Есть ли такой?
В Linux числа в текстовых представлениях socket:[#]
являются номерами индексных дескрипторов сокетов, см. proc_pid_fd(5). Номера индексных дескрипторов предназначены для идентификации, но не для адресации (как в случае с путями файловой системы), поэтому вы не можете их open()
и так далее. Таким образом, номера индексных дескрипторов не являются (как вы выразились) «внутренними конечными точками», а просто идентификаторами (и даже не обязательно уникальными с течением времени, поскольку их можно использовать повторно).
Насколько мне известно, вы можете дублировать связанный с ним fd даже между процессами. Вы можете увидеть это в действии в моей программе проверки утечек Go fd, в функции NewSocketFd.
Помните, что оба fd (один в другом процессе, а также дубликат в вашем процессе) ссылаются на один и тот же сокет или файл, поэтому будьте осторожны, чтобы не изменить состояние ресурса, на который ссылается fd.
Используя дублированный fd, вы можете запросить конфигурацию сокетов; обратите внимание также на родственный каталог fdinfo
для fd
, который содержит псевдотекстовые файлы с подробностями fd, см. proc_pid_fdinfo(5).
Ответ TheDiveO дал мне подсказку. Его golang NewSocketFd использует системный вызов Linux pidfd_getfd, доступный начиная с ядра 5.4. Он может дублировать и fd процесса, открытого с помощью fd, созданного pidfd_open. Для операции pidfd_getfd требуются разрешения PTRACE_MODE_ATTACH_REALCREDS, а это означает, что, если вы не работаете от имени пользователя root, это будет работать только для получения fd дочернего процесса, а не просто любого случайного. Вот пример кода для записи в сокет другого процесса:
#include <stdio.h>
#include <stdlib.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>
static int pidfd_open (pid_t pid, unsigned flags)
{ return syscall (SYS_pidfd_open, pid, flags); }
static int pidfd_getfd (pid_t pid, unsigned targetfd, unsigned flags)
{ return syscall (SYS_pidfd_getfd, pid, targetfd, flags); }
int main (int argc, const char* const* argv)
{
if (argc != 3) {
printf ("Usage: %s <pid> <fd>\n", argv[0]);
return EXIT_SUCCESS;
}
int pidfd = pidfd_open (atoi(argv[1]), 0);
if (pidfd < 0) {
perror ("pidfd_open");
return EXIT_FAILURE;
}
int tfd = pidfd_getfd (pidfd, atoi(argv[2]), 0);
if (tfd < 0)
perror ("pidfd_getfd");
else {
static const char c_test_data[] = "hello world";
if (sizeof(c_test_data) != write (tfd, c_test_data, sizeof(c_test_data)))
perror ("write");
close (tfd);
}
close (pidfd);
return EXIT_SUCCESS;
}