¿Cómo cerrar forzosamente un socket en TIME_WAIT?

Ejecuto un programa concreto en linux que a veces se bloquea. Si lo abres rápidamente después de eso, escucha en el socket 49201 en lugar del 49200 como la primera vez. netstat revela que el 49200 está en estado TIME_WAIT.

¿Hay algún programa que pueda ejecutar para forzar inmediatamente a ese socket a salir del estado TIME_WAIT?

Solución
/etc/init.d/networking restart

Permítanme explicar. El Protocolo de Control de Transmisión (TCP) está diseñado para ser un protocolo de transmisión de datos bidireccional, ordenado y fiable entre dos puntos finales (programas). En este contexto, el término fiable significa que retransmitirá los paquetes si se pierden en el medio. TCP garantiza la fiabilidad mediante el envío de paquetes de acuse de recibo (ACK) para uno o varios paquetes recibidos de su homólogo.

Lo mismo ocurre con las señales de control, como la solicitud/respuesta de terminación. RFC 793 define el estado TIME-WAIT de la siguiente manera:

TIME-WAIT - representa la espera de pase el tiempo suficiente para estar seguro de que que el TCP remoto ha recibido el acuse de recibo de su conexión solicitud de terminación.

Véase el siguiente diagrama de estado TCP:

TCP es un protocolo de comunicación bidireccional, por lo que cuando se establece la conexión, no hay diferencia entre el cliente y el servidor. Además, cualquiera de los dos puede llamar al cierre, y ambos peers necesitan estar de acuerdo en el cierre para cerrar completamente una conexión TCP establecida.

Llamemos al primero que llama al quits el cerrador activo, y al otro peer el cerrador pasivo. Cuando el cerrador activo envía FIN, el estado pasa a FIN-WAIT-1. Luego recibe un ACK por el FIN enviado y el estado pasa a FIN-WAIT-2. Cuando recibe FIN también del abridor pasivo, el abridor activo envía el ACK de FIN y el estado pasa a TIME-WAIT. En caso de que el cerrador pasivo no haya recibido el ACK del segundo FIN, retransmitirá el paquete FIN.

El RFC 793 establece el TIME-OUT en el doble del Tiempo Máximo de Vida del Segmento, o 2MSL. Dado que MSL, el tiempo máximo que un paquete puede vagar por Internet, está fijado en 2 minutos, 2MSL es 4 minutos. Como no hay ACK a un ACK, el emisor activo no puede hacer otra cosa que esperar 4 minutos si se atiene correctamente al protocolo TCP/IP, por si acaso el emisor pasivo no ha recibido el ACK a su FIN (teóricamente).

En realidad, los paquetes perdidos son probablemente raros, y muy raros si todo ocurre dentro de la LAN o dentro de una sola máquina.

Para responder a la pregunta textualmente, ¿Cómo cerrar forzosamente un socket en TIME_WAIT?, me seguiré ciñendo a mi respuesta original:

/etc/init.d/networking restart

En la práctica, lo programaría para que ignorara el estado TIME-WAIT usando la opción SO_REUSEADDR como mencionó WMR. ¿Qué hace exactamente SO_REUSEADDR?

Esta opción de socket le dice al kernel que incluso si este puerto está ocupado (en el estado TIME_WAIT), siga adelante y reutilizarlo de todos modos. Si está ocupado, pero con otro estado, aún obtendrá un error de dirección ya en uso. Es es útil si su servidor ha sido apagado apagado, y luego reiniciado inmediatamente mientras los sockets siguen activos en su puerto. Usted debe ser consciente de que si cualquier dato inesperado entra, puede confundir a su servidor, pero mientras esto es posible, no es probable.

Comentarios (5)

No sé si tienes el código fuente de ese programa en particular que estás ejecutando, pero si es así podrías simplemente establecer SO_REUSEADDR a través de setsockopt(2) que te permite enlazar en la misma dirección local incluso si el socket está en estado TIME_WAIT (a menos que ese socket esté escuchando activamente, ver socket(7)).

Para más información sobre el estado TIME_WAIT vea Unix socket FAQ.

Comentarios (5)

Hasta donde yo sé, no hay manera de cerrar el socket a la fuerza fuera de escribir un mejor manejador de señales en su programa, pero hay un archivo /proc que controla cuánto tiempo toma el tiempo de espera. El archivo es

/proc/sys/net/ipv4/tcp_tw_recycle

y puedes establecer el tiempo de espera en 1 segundo haciendo esto:

echo 1 > /proc/sys/net/ipv4/tcp_tw_recycle 

Sin embargo, esta página contiene una advertencia sobre posibles problemas de fiabilidad al establecer esta variable.

También hay un archivo relacionado

/proc/sys/net/ipv4/tcp_tw_reuse

que controla si los sockets TIME_WAIT pueden ser reutilizados (presumiblemente sin tiempo de espera).

Por cierto, la documentación del kernel le advierte que no cambie ninguno de estos valores sin 'asesoramiento/solicitud de expertos técnicos'. Cosa que yo no soy.

El programa debe haber sido escrito para intentar un enlace al puerto 49200 y luego incrementar en 1 si el puerto ya está en uso. Por lo tanto, si usted tiene el control del código fuente, podría cambiar este comportamiento para esperar unos segundos y vuelva a intentarlo en el mismo puerto, en lugar de incrementar.

Comentarios (3)