Процесс, запущенный из системной команды в C, наследует родительские fd's

У меня есть пример приложения SIP-сервера, который прослушивает как TCP, так и UDP-порты 5060. В какой-то момент кода я делаю систему ( "pppd файл /etc/ppp/myoptions &" );

После этого, если я делаю netstat -apn, он показывает мне, что порты 5060 также открыты для pppd! Есть ли способ избежать этого? Является ли это стандартным поведением системной функции в Linux?

Спасибо, Elison

Ответ 1

Да, по умолчанию всякий раз, когда вы форкируете процесс (который system делает), ребенок наследует все дескрипторы родительского файла. Если ребенку не нужны эти дескрипторы, он ДОЛЖЕН закрыть их. Способ сделать это с помощью system (или любого другого метода, который выполняет fork + exec) - установить флаг FD_CLOEXEC во всех дескрипторах файлов, которые не должны использоваться дочерними процессами вашего процесса. Это заставит их автоматически закрываться всякий раз, когда какой-либо дочерний элемент выполняет какую-либо другую программу.

В общем, ЛЮБОЕ ВРЕМЯ ваша программа открывает ЛЮБОЙ ВИДЕНЬ файлового дескриптора, который будет жить в течение длительного периода времени (например, в качестве примера в качестве примера в гнезде для прослушивания), и который не должен использоваться совместно с детьми, вы должны сделать

fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);

в дескрипторе файла.


По состоянию на 2016 год? пересмотр POSIX.1, вы можете использовать флаг SOCK_CLOEXEC or'd в качестве сокета, чтобы автоматически получить это поведение при создании сокета:

listenfd = socket(AF_INET, SOCK_STREAM|SOCK_CLOEXEC, 0);
bind(listenfd, ...
listen(listemfd, ...

который гарантирует, что он будет закрыт должным образом, даже если какой-либо другой одновременно выполняющийся поток выполняет вызов system или fork + exec. К счастью, этот флаг долгое время поддерживался на Linux и BSD-Unix (но не OSX, к сожалению).

Ответ 2

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

Что вы должны сделать, это танец fork()/exec(). Это похоже на это

if(!fork()){
     //close file descriptors
     ...

    execlp("pppd", "pppd", "file", "/etc/ppp/myoptions", NULL);
    perror("exec");
    exit(-1);
}

Ответ 3

Да, это стандартное поведение fork() в Linux, из которого реализовано system().

Идентификатор, возвращаемый из вызова socket(), является допустимым файловым дескриптором. Это значение можно использовать с файловыми функциями, такими как read(), write(), ioctl() и close().

Обратно, что каждый дескриптор файла является сокетом, это не так. Нельзя открыть обычный файл с помощью open() и передать этот дескриптор, например, bind() или listen().

Когда вы вызываете system(), дочерний процесс наследует те же файловые дескрипторы, что и родительский. Вот как stdout (0), stdin (1) и stderr (2) наследуются дочерними процессами. Если вы организуете открытие сокета с файловым дескриптором 0, 1 или 2, дочерний процесс наследует этот сокет как один из стандартных дескрипторов файла ввода/вывода.

Ваш дочерний процесс наследует каждый открытый файловый дескриптор от родителя, включая сокет, который вы открыли.

Ответ 4

Как утверждали другие, это стандартное поведение, от которого зависят программы.

Когда дело доходит до предотвращения этого, у вас есть несколько вариантов. Во-первых, закрывает все дескрипторы файлов после fork(), как предлагает Дейв. Во-вторых, существует поддержка POSIX для использования fcntl с FD_CLOEXEC для установки бит "close on exec" на основе fd.

Наконец, однако, поскольку вы упоминаете, что вы работаете в Linux, есть набор изменений, которые позволят вам установить бит прямо в момент открытия. Естественно, это зависит от платформы. Обзор можно найти на http://udrepper.livejournal.com/20407.html

Это означает, что вы можете использовать поразрядный или с типом в вызове создания сокета, чтобы установить флаг SOCK_CLOEXEC. Если вы используете ядро ​​2.6.27 или новее, то есть.

Ответ 5

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