2011-08-04 13 views
10

Tengo una aplicación de muestra de un servidor SIP escuchando en los puertos tcp y udp 5060. En algún punto del código, hago un sistema ("archivo pppd/etc/ppp/myoptions & ");Proceso iniciado desde el comando del sistema en C hereda el padre fd

Después de esto, si hago un netstat -apn, ¡me muestra que los puertos 5060 también están abiertos para pppd! ¿Hay algún método para evitar esto? ¿Este comportamiento estándar del sistema funciona en Linux?

Gracias, Elison

Respuesta

14

Sí, de forma predeterminada cada vez que bifurca un proceso (que system hace), el elemento secundario hereda todos los descriptores de archivos del elemento principal. Si el niño no necesita esos descriptores, DEBERÍA cerrarlos. La forma de hacerlo con system (o cualquier otro método que haga un fork + exec) es establecer el indicador FD_CLOEXEC en todos los descriptores de archivo que no deberían ser utilizados por los hijos del proceso. Esto hará que se cierren automáticamente cada vez que un niño ejecute algún otro programa.

En general, CUALQUIER TIEMPO su programa abre CUALQUIER TIPO de descriptor de archivo que vivirá durante un período prolongado (como un socket de escucha en su ejemplo) y que no se debe compartir con niños, debe hacer

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

en el descriptor de archivo.


A partir del 2016? revisión de POSIX.1, puede utilizar la bandera SOCK_CLOEXEC usando OR en el tipo de la toma para conseguir este comportamiento de forma automática cuando se crea el socket:

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

cuales guarentees que se cerrará correctamente, incluso si algún otro Al ejecutar simultáneamente hilo hace una llamada system o fork + exec. Afortunadamente, esta bandera ha sido admitida por un tiempo en Linux y unixes BSD (pero no en OSX, desafortunadamente).

+0

Sí, me encontré con esto. Hasta el momento en que cambie mi código para usar fork()/exec() usaré este método solamente. –

+2

Con POSIX 2008 o GNU, también puede usar el indicador 'O_CLOEXEC' al abrir el archivo. Esto evita tener que hacer una llamada por separado, y hace que la operación * atómica *, que importa si otro hilo (o controlador de señal) podría fork-and-exec entre la llamada 'open' y la llamada' fcntl'. –

3

probablemente debería evitar por completo la función system(). Es intrínsecamente peligroso, ya que invoca el caparazón, que puede ser alterado y no portátil, incluso entre Unicies.

Lo que debe hacer es la danza fork()/exec(). Es algo parecido a este proceso actual

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

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

es posible que desee agregar algunos consejos sobre cómo saber qué fds cerrar si hay algo más que el socket. – Spudd86

+0

Gracias. Sí, definitivamente reemplazaré el sistema() con fork()/exec(). ¡Actualmente mi código llama al sistema() muchas veces! Utilizo el sistema porque puedo obtener el estado de retorno con WIFEXITED y es mucho más rápido de codificar. Por cierto, ¿qué método sugieres que siga? Si necesito un montón de cosas para hacer desde mi código C, como escribir /etc/resolv.conf, iniciar pppd, kill pppd, ejecutar iptables -F, etc. En este momento todo esto está hecho mi sistema(). –

-1

system() copias y luego lanzar un niño en la parte superior de la misma. (Proceso actual ya no está ahí. Que es probablemente la razón por pppd utiliza 5060. Puede probar fork()/exec() para crear un proceso hijo y tener padres con vida.

+1

-1: el sistema no reemplaza/termina el proceso principal. De hecho, '' 'system()' '' utiliza '' 'fork()' ''/'' 'exec()' ''. –

+0

Heath Hunnicutt: Gracias, ¿entonces después de 'system()' 2 procesos existen? padre e hijo? – hari

+0

Después de '' 'system()' '', hay 2 procesos, el principal y el secundario, pero la llamada aún no ha regresado. La implementación '' 'system()' '' llama a '' 'waitpid''' en el PID secundario. En el caso de '' 'pppd''', el proceso self-backgrounds (' '' fork'''/'' exec''' sin '' 'waitpid'''), por lo que hay momentáneamente * tres * procesos, dos de ppd, uno de los padres. En el momento en que '' 'system''' regresa, el hijo ha salido, porque el valor de retorno dentro de C contiene el valor proporcionado a' '' exit''' por el programa engendrado. Una vez que '' 'system''' regrese, el hijo debe haber salido. –

1

Sí, este es un comportamiento estándar de fork() en Linux, a partir del cual se implementa system() .

el identificador de regresar de la llamada socket() es un descriptor de archivo válido. Este valor se puede utilizar con funciones orientadas a archivos, como read(), write(), ioctl() y close().

lo contrario, que cada descriptor de archivo es un zócalo, yo no es verdad No se puede abrir un archivo normal con open() y pasar ese descriptor a, por ejemplo, bind() o listen().

Cuando llama al system(), el proceso secundario hereda los mismos descriptores de archivos que el elemento primario. Así es como stdout (0), stdin (1) y stderr (2) son heredados por procesos secundarios. Si organiza abrir un socket con un descriptor de archivo de 0, 1 o 2, el proceso secundario heredará ese socket como uno de los descriptores de archivos de E/S estándar.

Su proceso secundario está heredando cada descriptor de archivo abierto del principal, incluido el socket que abrió.

+0

Abro el servidor SIP en 2 interfaces de red para puertos udp y tcp. Entonces, antes de la llamada al sistema(), netstat me muestra 4 entradas para 5060 - 2 tcp para eth0, 2 udp para eth1. Después de la llamada al sistema (pppd ...), netstat me da 6 entradas para pppd(). pppd hereda 2 sockets para eth0. Puede ser porque una vez que se establece el enlace pppd, cierro los sockets del servidor SIP en la interfaz eth1 en mi aplicación principal. Esto explica por qué solo 2 son heredados. Llamo a netstat desde un script de shell a intervalos de 2 segundos, por lo que puede fallar para mostrarme cuándo pppd había heredado los 4 sockets. –

1

Como han dicho otros, este es un comportamiento estándar del que dependen los programas.

Cuando se trata de prevenirlo, tiene algunas opciones. En primer lugar está cerrando todos los descriptores de archivos después del fork(), como sugiere Dave. En segundo lugar, está el soporte POSIX para usar fcntl con FD_CLOEXEC para establecer un bit 'close on exec' por fd.

Por último, sin embargo, dado que usted menciona que se está ejecutando en Linux, hay un conjunto de cambios diseñados para permitirle configurar el bit justo en el momento de abrir las cosas. Naturalmente, esto depende de la plataforma. Se puede encontrar una descripción general en http://udrepper.livejournal.com/20407.html

Lo que esto significa es que puede usar un bitwise o con el 'tipo' en su llamada de creación de socket para establecer el indicador SOCK_CLOEXEC. Siempre que esté ejecutando Kernel 2.6.27 o más tarde, eso es.

+0

Ya estoy ejecutando 2.6.30 así que usaré fcntl con FD_CLOEXEC. –

Cuestiones relacionadas