¿Cuándo se requiere la opción TCP SO_LINGER (0)?

95

Creo que entiendo el significado formal de la opción. En algún código heredado que estoy manejando ahora, se usa la opción. El cliente se queja de RST como respuesta a FIN desde su lado al cerrar la conexión desde su lado.

No estoy seguro de poder quitarlo de manera segura, ya que no entiendo cuándo debe usarse.

¿Puede dar un ejemplo de cuándo sería necesaria la opción?

dimba
fuente
1
Deberías eliminarlo. No debe usarse en código de producción. La única vez que vi que se utilizó fue como resultado de una referencia no válida.
Marqués de Lorne

Respuestas:

82

La razón típica para establecer un SO_LINGERtiempo de espera de cero es evitar un gran número de conexiones en el TIME_WAITestado, lo que inmoviliza todos los recursos disponibles en un servidor.

Cuando una conexión TCP se cierra limpiamente, el extremo que inició el cierre ("cierre activo") termina con la conexión TIME_WAITdurante varios minutos. Entonces, si su protocolo es uno en el que el servidor inicia el cierre de la conexión e involucra una gran cantidad de conexiones de corta duración, entonces podría ser susceptible a este problema.

Sin embargo, esta no es una buena idea, TIME_WAITexiste por una razón (para garantizar que los paquetes perdidos de conexiones antiguas no interfieran con las conexiones nuevas). Es una mejor idea rediseñar su protocolo a uno en el que el cliente inicie la conexión, si es posible.

coste y flete
fuente
3
Estoy totalmente de acuerdo. He visto una aplicación de monitoreo que estaba iniciando muchas (algunos miles de conexiones de corta duración cada X segundos), y tenía problemas para escalar más (mil conexiones más). No sé por qué, pero la aplicación no respondió. Alguien sugirió SO_LINGER = true, TIME_WAIT = 0 para liberar rápidamente los recursos del sistema operativo, y después de una breve investigación probamos esta solución con muy buenos resultados. TIME_WAIT ya no es un problema para esta aplicación.
bartosz.r
24
Estoy en desacuerdo. Se debe diseñar un protocolo de nivel de aplicación encima de TCP de tal manera que el cliente siempre inicie el cierre de la conexión. De esa manera, TIME_WAITse sentará al lado del cliente sin hacerle daño. Recuerde como dice en "Programación de red UNIX" tercera edición (Stevens et al) página 203: "El estado TIME_WAIT es su amigo y está ahí para ayudarnos. En lugar de tratar de evitar el estado, debemos entenderlo (Sección 2.7) . "
mgd
8
¿Qué pasa si un cliente quiere abrir 4000 conexiones cada 30 segundos (¡esta aplicación de monitoreo es un cliente! Porque inicia la conexión)? Sí, podemos rediseñar la aplicación, agregar algunos agentes locales en la infraestructura, cambiar el modelo para impulsar. Pero si ya tenemos una aplicación de este tipo y crece, entonces podemos hacer que funcione ajustando twe linger. Cambia un parámetro y de repente tiene una aplicación que funciona, sin invertir un presupuesto para implementar una nueva arquitectura.
bartosz.r
3
@ bartosz.r: Solo digo que usar SO_LINGER con timeout 0 debería ser realmente un último recurso. De nuevo, en la tercera edición de "Programación de red UNIX" (Stevens et al), página 203, también se indica que corre el riesgo de dañar los datos. Considere leer RFC 1337 donde puede ver por qué TIME_WAIT es su amigo.
mgd
7
@caf No, la solución clásica sería un grupo de conexiones, como se ve en todas las API de TCP de servicio pesado, por ejemplo, HTTP 1.1.
Marqués de Lorne
188

Para mi sugerencia, lea la última sección: "Cuándo usar SO_LINGER con timeout 0" .

Antes de llegar a eso, una pequeña conferencia sobre:

  • Terminación TCP normal
  • TIME_WAIT
  • FIN, ACKyRST

Terminación TCP normal

La secuencia de terminación de TCP normal se ve así (simplificada):

Tenemos dos pares: A y B

  1. A llama close()
    • A envía FINa B
    • A entra en FIN_WAIT_1estado
  2. B recibe FIN
    • B envía ACKa A
    • B entra en CLOSE_WAITestado
  3. A recibe ACK
    • A entra en FIN_WAIT_2estado
  4. B llama close()
    • B envía FINa A
    • B entra en LAST_ACKestado
  5. A recibe FIN
    • A envía ACKa B
    • A entra en TIME_WAITestado
  6. B recibe ACK
    • B pasa al CLOSEDestado, es decir, se elimina de las tablas de conectores

TIEMPO DE ESPERA

Por lo tanto, el par que inicia la terminación, es decir, llama close()primero, terminará en el TIME_WAITestado.

Para comprender por qué el TIME_WAITestado es nuestro amigo, lea la sección 2.7 de la tercera edición de "Programación de redes UNIX" de Stevens et al (página 43).

Sin embargo, puede ser un problema con muchos enchufes en TIME_WAIT estado en un servidor ya que eventualmente podría evitar que se acepten nuevas conexiones.

Para solucionar este problema, he visto muchas sugerencias para configurar la opción de socket SO_LINGER con el tiempo de espera 0 antes de llamar close(). Sin embargo, esta es una mala solución ya que hace que la conexión TCP finalice con un error.

En su lugar, diseñe su protocolo de aplicación para que la terminación de la conexión siempre se inicie desde el lado del cliente. Si el cliente siempre sabe cuándo ha leído todos los datos restantes, puede iniciar la secuencia de terminación. Por ejemplo, un navegador sabe por el Content-Lengthencabezado HTTP cuándo ha leído todos los datos y puede iniciar el cierre. (Sé que en HTTP 1.1 lo mantendrá abierto durante un tiempo para una posible reutilización y luego lo cerrará).

Si el servidor necesita cerrar la conexión, diseñe el protocolo de aplicación para que el servidor le pida al cliente que llame close().

Cuándo usar SO_LINGER con tiempo de espera 0

Una vez más, de acuerdo con la tercera edición de "Programación de red UNIX", página 202-203, la configuración SO_LINGERcon tiempo de espera 0 antes de la llamada close()hará que no se inicie la secuencia de terminación normal .

En cambio, el par que configura esta opción y llama close()enviará un RST(restablecimiento de conexión) que indica una condición de error y así es como se percibirá en el otro extremo. Por lo general, verá errores como "Conexión restablecida por par".

Por lo tanto, en la situación normal, es una muy mala idea establecer SO_LINGERcon el tiempo de espera 0 antes de llamar close(); de ahora en adelante, se llama cierre abortivo. , en una aplicación de servidor.

Sin embargo, cierta situación justifica hacerlo de todos modos:

  • Si el cliente de su aplicación de servidor se comporta mal (se agota el tiempo de espera, devuelve datos no válidos, etc.), un cierre abortivo tiene sentido para evitar quedarse atascado CLOSE_WAITo terminar en el TIME_WAITestado.
  • Si debe reiniciar la aplicación de su servidor, que actualmente tiene miles de conexiones de cliente, puede considerar configurar esta opción de socket para evitar miles de sockets de servidor TIME_WAIT(al llamar close()desde el extremo del servidor), ya que esto podría evitar que el servidor obtenga puertos disponibles para nuevas conexiones de cliente. después de reiniciarse.
  • En la página 202 del libro antes mencionado, dice específicamente: "Hay ciertas circunstancias que justifican el uso de esta función para enviar un cierre abortivo. Un ejemplo es un servidor de terminal RS-232, que puede bloquearse para siempre al CLOSE_WAITintentar entregar datos a un terminal atascado puerto, pero restablecería correctamente el puerto atascado si tuviera RSTque descartar los datos pendientes ".

Recomendaría este extenso artículo que creo que da una muy buena respuesta a tu pregunta.

mgd
fuente
6
TIME_WAITes un amigo solo cuando no comienza a causar problemas: stackoverflow.com/questions/1803566/…
Pacerier
2
¿Y qué pasa si estás escribiendo un servidor web? ¿Cómo "le dices al cliente que inicie un cierre"?
Shaun Neal
2
@ShaunNeal, obviamente no lo haces. Pero un cliente / navegador bien escrito iniciará el cierre. Si el cliente no se está portando bien, afortunadamente tenemos el asesinato TIME_WAIT para asegurarnos de que no nos quedemos sin descriptores de socket y puertos efímeros.
mgd
16

Cuando el tiempo de espera está activado pero el tiempo de espera es cero, la pila TCP no espera a que se envíen los datos pendientes antes de cerrar la conexión. Los datos podrían perderse debido a esto, pero al configurar persistir de esta manera, lo acepta y solicita que la conexión se restablezca de inmediato en lugar de cerrarla correctamente. Esto hace que se envíe un RST en lugar del FIN habitual.

Gracias a EJP por su comentario, consulte aquí para obtener más detalles.

Len Holgate
fuente
1
Entiendo esto. lo que estoy pidiendo es un ejemplo "realista" cuando nos gustaría utilizar el restablecimiento completo.
dimba
5
Siempre que desee abortar una conexión; así que si su protocolo falla en la validación y tiene un cliente que le dice tonterías, de repente abortaría la conexión con un RST, etc.
Len Holgate
5
Estás confundiendo un tiempo de espera sin demora con el tiempo de espera. Linger off significa que close () no bloquea. Permanecer con un tiempo de espera positivo significa que close () bloquea hasta el tiempo de espera. Permanecer con un tiempo de espera cero causa RST, y de esto se trata la pregunta.
Marqués de Lorne
2
Sí, tienes razón. Ajustaré la respuesta para corregir mi terminología.
Len Holgate
6

Si puede eliminar la persistencia en su código de forma segura o no depende del tipo de su aplicación: ¿es un "cliente" (abriendo conexiones TCP y cerrándolas activamente primero) o es un "servidor" (escuchando un TCP abierto y cerrarlo después de que el otro lado inició el cierre)?

Si su aplicación tiene el sabor de un "cliente" (cerrando primero) Y usted inicia y cierra una gran cantidad de conexiones a diferentes servidores (por ejemplo, cuando su aplicación es una aplicación de monitoreo que supervisa la accesibilidad de una gran cantidad de servidores diferentes) su aplicación tiene el problema de que todas las conexiones de sus clientes están bloqueadas en el estado TIME_WAIT. Luego, recomendaría acortar el tiempo de espera a un valor más pequeño que el predeterminado para seguir apagando correctamente pero liberar los recursos de conexiones del cliente antes. No establecería el tiempo de espera en 0, ya que 0 no se apaga correctamente con FIN pero aborta con RST.

Si su aplicación tiene el sabor de un "cliente" y tiene que buscar una gran cantidad de archivos pequeños del mismo servidor, no debe iniciar una nueva conexión TCP por archivo y terminar en una gran cantidad de conexiones de cliente en TIME_WAIT, pero mantenga la conexión abierta y obtenga todos los datos a través de la misma conexión. La opción de persistencia puede y debe eliminarse.

Si su aplicación es un "servidor" (cierre en segundo lugar como reacción al cierre de un par), al cerrar () su conexión se cierra con gracia y los recursos se liberan ya que no ingresa al estado TIME_WAIT. No se debe utilizar Linger. Pero si su aplicación de servidor tiene un proceso de supervisión que detecta conexiones abiertas inactivas inactivas durante un tiempo prolongado (debe definirse "largo"), puede cerrar esta conexión inactiva por su parte, verla como una especie de manejo de errores, con un cierre abortivo. Esto se hace estableciendo el tiempo de espera persistente en 0. close () luego enviará un RST al cliente, diciéndole que está enojado :-)

Grandswiss
fuente
1

En los servidores, es posible que desee enviar en RSTlugar de FINdesconectar a los clientes que se comportan mal. Eso omite FIN-WAITseguido de TIME-WAITestados de socket en el servidor, lo que evita que se agoten los recursos del servidor y, por lo tanto, protege de este tipo de ataque de denegación de servicio.

Maxim Egorushkin
fuente
0

Me gusta la observación de Maxim de que los ataques de DOS pueden agotar los recursos del servidor. También sucede sin un adversario realmente malicioso.

Algunos servidores tienen que lidiar con el 'ataque DOS involuntario' que ocurre cuando la aplicación cliente tiene un error de fuga de conexión, donde siguen creando una nueva conexión para cada nuevo comando que envían a su servidor. Y luego tal vez eventualmente cerrando sus conexiones si golpean la presión del GC, o tal vez las conexiones eventualmente se agoten.

Otro escenario es cuando "todos los clientes tienen la misma dirección TCP". Entonces, las conexiones del cliente se distinguen solo por los números de puerto (si se conectan a un solo servidor). Y si los clientes comienzan a ciclar rápidamente las conexiones de apertura / cierre por cualquier motivo, pueden agotar el espacio de tupla (dirección de cliente + puerto, IP de servidor + puerto).

Por lo tanto, creo que es mejor recomendar a los servidores que cambien a la estrategia Linger-Zero cuando vean una gran cantidad de sockets en el estado TIME_WAIT; aunque no corrige el comportamiento del cliente, podría reducir el impacto.

Tim Lovell-Smith
fuente
0

El socket de escucha en un servidor puede usar el tiempo de espera con 0 para tener acceso al enlace de regreso al socket inmediatamente y para restablecer cualquier cliente cuyas conexiones aún no hayan terminado de conectarse. TIME_WAIT es algo que solo es interesante cuando tiene una red de múltiples rutas y puede terminar con paquetes mal ordenados o, de lo contrario, está lidiando con un pedido / tiempo de llegada extraño de paquetes de red.

Gregg Wonderly
fuente