Quand l'option TCP SO_LINGER (0) est-elle requise?


95

Je pense que je comprends le sens formel de l'option. Dans certains codes hérités que je gère actuellement, l'option est utilisée. Le client se plaint de RST en réponse à FIN de son côté sur la connexion à proximité de son côté.

Je ne suis pas sûr de pouvoir le supprimer en toute sécurité, car je ne comprends pas quand il doit être utilisé.

Pouvez-vous donner un exemple du moment où l'option serait requise?


1
Vous devriez le supprimer. Il ne doit pas être utilisé dans le code de production. La seule fois où je l'ai vu utilisé était le résultat d'un repère invalide.
Marquis of Lorne

Réponses:


82

La raison typique pour définir un SO_LINGERdélai d'expiration de zéro est d'éviter un grand nombre de connexions dans leTIME_WAIT état, bloquant toutes les ressources disponibles sur un serveur.

Lorsqu'une connexion TCP est fermée proprement, la fin qui a initié la fermeture («fermeture active») se termine par la connexion TIME_WAITpendant plusieurs minutes. Donc, si votre protocole est celui où le serveur initie la fermeture de la connexion et implique un très grand nombre de connexions de courte durée, il peut être sensible à ce problème.

Ce n'est pas une bonne idée, cependant - TIME_WAITexiste pour une raison (pour s'assurer que les paquets parasites d'anciennes connexions n'interfèrent pas avec les nouvelles connexions). Il est préférable de reconcevoir votre protocole en un protocole dans lequel le client initie la fermeture de la connexion, si possible.


3
Je suis tout à fait d'accord. J'ai vu une application de surveillance qui en lançait beaucoup (quelques milliers de connexions de courte durée toutes les X secondes), et elle avait du mal à évoluer plus grand (mille connexions de plus). Je ne sais pas pourquoi, mais l'applicatif n'était pas réactif. Quelqu'un a suggéré SO_LINGER = true, TIME_WAIT = 0 pour libérer rapidement les ressources du système d'exploitation, et après une courte enquête, nous avons essayé cette solution avec de très bons résultats. Le TIME_WAIT n'est plus un problème pour cette application.
bartosz.r

24
Je ne suis pas d'accord. Un protocole de niveau application reposant sur TCP doit être conçu de telle manière que le client initie toujours la fermeture de la connexion. De cette façon, la TIME_WAITvolonté s'assiéra au client sans nuire. Rappelez-vous comme il est dit dans "UNIX Network Programming" troisième édition (Stevens et al) page 203: "L'état TIME_WAIT est votre ami et est là pour nous aider. Au lieu d'essayer d'éviter l'état, nous devons le comprendre (Section 2.7) . "
mgd

8
Que faire si un client souhaite ouvrir 4000 connexions toutes les 30 secondes (cette application de surveillance est un client! Car elle initie la connexion)? Oui, nous pouvons redessiner l'application, ajouter des agents locaux dans l'infrastructure, changer le modèle pour pousser. Mais si nous avons déjà une telle application et qu'elle se développe, nous pouvons la faire fonctionner en réglant twe persister. Vous changez un paramètre et vous avez soudainement une application qui fonctionne, sans investir un budget pour mettre en œuvre une nouvelle architecture.
bartosz.r

3
@ bartosz.r: Je dis seulement que l'utilisation de SO_LINGER avec timeout 0 devrait vraiment être un dernier recours. Encore une fois, dans la troisième édition de "UNIX Network Programming" (Stevens et al) page 203, il est également indiqué que vous risquez de corrompre les données. Envisagez de lire la RFC 1337 où vous pouvez voir pourquoi TIME_WAIT est votre ami.
mgd

7
@caf Non, la solution classique serait un pool de connexions, comme on le voit dans chaque API TCP robuste, par exemple HTTP 1.1.
Marquis of Lorne

188

Pour ma suggestion, veuillez lire la dernière section: "Quand utiliser SO_LINGER avec timeout 0" .

Avant d'en venir à cela, une petite conférence sur:

  • Terminaison TCP normale
  • TIME_WAIT
  • FIN, ACKetRST

Terminaison TCP normale

La séquence de terminaison TCP normale ressemble à ceci (simplifié):

Nous avons deux pairs: A et B

  1. A appelle close()
    • A envoie FINà B
    • A entre en FIN_WAIT_1état
  2. B reçoit FIN
    • B envoie ACKà A
    • B entre dans l' CLOSE_WAITétat
  3. A reçoit ACK
    • A entre en FIN_WAIT_2état
  4. Appels B close()
    • B envoie FINà A
    • B entre dans l' LAST_ACKétat
  5. A reçoit FIN
    • A envoie ACKà B
    • A entre en TIME_WAITétat
  6. B reçoit ACK
    • B passe à l' CLOSEDétat - c'est-à-dire qu'il est supprimé des tables de socket

TEMPS D'ATTENTE

Ainsi, le pair qui initie la terminaison - c'est-à-dire appelle en close()premier - se retrouvera dans l' TIME_WAITétat.

Pour comprendre pourquoi l' TIME_WAITÉtat est notre ami, veuillez lire la section 2.7 de la troisième édition de "UNIX Network Programming" de Stevens et al (page 43).

Cependant, cela peut être un problème avec beaucoup de sockets dans TIME_WAIT état sur un serveur car cela pourrait éventuellement empêcher de nouvelles connexions d'être acceptées.

Pour contourner ce problème, j'ai vu beaucoup suggérer de définir l'option de socket SO_LINGER avec timeout 0 avant d'appeler close(). Cependant, il s'agit d'une mauvaise solution car elle provoque l'arrêt de la connexion TCP avec une erreur.

Au lieu de cela, concevez votre protocole d'application de sorte que la fin de la connexion soit toujours lancée du côté client. Si le client sait toujours quand il a lu toutes les données restantes, il peut lancer la séquence de terminaison. À titre d'exemple, un navigateur sait à partir de l' Content-Lengthen-tête HTTP quand il a lu toutes les données et peut lancer la fermeture. (Je sais que dans HTTP 1.1, il le gardera ouvert pendant un certain temps pour une éventuelle réutilisation, puis le fermera.)

Si le serveur doit fermer la connexion, concevez le protocole d'application de sorte que le serveur demande au client d'appeler close() .

Quand utiliser SO_LINGER avec timeout 0

Encore une fois, selon « réseau UNIX programmation » troisième édition , page 202-203, réglage SO_LINGERavec temporisation 0 avant d'appeler close()provoquera la séquence de terminaison normale ne doit être initié.

Au lieu de cela, le pair définissant cette option et appelant close()enverra une RST(réinitialisation de la connexion) qui indique une condition d'erreur et c'est ainsi qu'elle sera perçue à l'autre extrémité. Vous verrez généralement des erreurs telles que «réinitialisation de la connexion par le pair».

Par conséquent, dans la situation normale, c'est une très mauvaise idée de définir SO_LINGERavec timeout 0 avant d'appeler close()- à partir de maintenant appelé abortive close - dans une application serveur.

Cependant, certaines situations le justifient quand même:

  • Si le client de votre application serveur se comporte mal (expire, renvoie des données non valides, etc.), une fermeture avortée a du sens pour éviter d'être bloqué CLOSE_WAITou de se retrouver dans l' TIME_WAITétat.
  • Si vous devez redémarrer votre application serveur qui a actuellement des milliers de connexions client, vous pouvez envisager de définir cette option de socket pour éviter des milliers de sockets serveur TIME_WAIT(lors d'un appel close()depuis le serveur) car cela pourrait empêcher le serveur d'obtenir les ports disponibles pour les nouvelles connexions client après avoir été redémarré.
  • À la page 202 du livre susmentionné, il est spécifiquement indiqué: "Il existe certaines circonstances qui justifient l'utilisation de cette fonctionnalité pour envoyer une fermeture avortée. Un exemple est un serveur de terminal RS-232, qui peut se bloquer indéfiniment en CLOSE_WAITessayant de fournir des données à un terminal bloqué. port, mais réinitialiserait correctement le port bloqué s’il réussissait RSTà supprimer les données en attente. »

Je recommanderais ce long article qui, je crois, donne une très bonne réponse à votre question.


6
TIME_WAITest un ami seulement quand il ne commence pas à causer des problèmes: stackoverflow.com/questions/1803566/…
Pacerier

2
alors que faire si vous écrivez un serveur Web? comment «dites-vous au client d’initier une clôture»?
Shaun Neal

2
@ShaunNeal, ce n'est évidemment pas le cas. Mais un client / navigateur bien écrit lancera la clôture. Si le client ne se comporte pas bien, heureusement, nous avons l'assassinat de TIME_WAIT pour nous assurer que nous ne manquons pas de descripteurs de socket et de ports éphémères.
mgd

16

Lorsque l'attente est activée mais que le délai d'attente est nul, la pile TCP n'attend pas l'envoi des données en attente avant de fermer la connexion. Des données pourraient être perdues à cause de cela, mais en réglant Linger de cette façon, vous acceptez cela et demandez que la connexion soit réinitialisée immédiatement plutôt que fermée correctement. Cela provoque l'envoi d'un RST au lieu du FIN habituel.

Merci à EJP pour son commentaire, voir ici pour plus de détails.


1
Je comprends ça. ce que je demande, c'est un exemple «réaliste» lorsque nous aimerions utiliser la réinitialisation matérielle.
dimba

5
Chaque fois que vous souhaitez interrompre une connexion; donc si votre protocole échoue à la validation et qu'un client vous parle tout d'un coup, vous abandonnez la connexion avec un RST, etc.
Len Holgate

5
Vous confondez un délai d'attente zéro avec un temps mort. S'attarder signifie que close () ne bloque pas. S'attarder avec un délai d'attente positif signifie que close () bloque jusqu'à l'expiration du délai. S'attarder avec un délai d'expiration nul provoque RST, et c'est de cela qu'il s'agit.
Marquis of Lorne

2
Oui, vous avez raison. Je vais ajuster la réponse pour corriger ma terminologie.
Len Holgate

6

Le fait que vous puissiez supprimer la persistance de votre code en toute sécurité ou non dépend du type de votre application: est-ce un «client» (ouverture des connexions TCP et le fermer activement en premier) ou est-ce un «serveur» (écoute d'un TCP ouvert et le fermer après que l'autre côté a lancé la fermeture)?

Si votre application a la saveur d'un «client» (fermer en premier) ET que vous initiez et fermez un grand nombre de connexions à différents serveurs (par exemple lorsque votre application est une application de surveillance supervisant l'accessibilité d'un grand nombre de serveurs différents), votre application a le problème que toutes vos connexions client sont bloquées dans l'état TIME_WAIT. Ensuite, je recommanderais de raccourcir le délai d'expiration à une valeur plus petite que la valeur par défaut pour toujours s'arrêter correctement mais libérer les ressources de connexions client plus tôt. Je ne définirais pas le délai d'expiration sur 0, car 0 ne s'arrête pas correctement avec FIN mais abandonne avec RST.

Si votre application a la saveur d'un «client» et doit récupérer une énorme quantité de petits fichiers sur le même serveur, vous ne devez pas initier une nouvelle connexion TCP par fichier et vous retrouver dans une énorme quantité de connexions client dans TIME_WAIT, mais gardez la connexion ouverte et récupérez toutes les données sur la même connexion. L'option Linger peut et doit être supprimée.

Si votre application est un «serveur» (fermez la seconde en réaction à la fermeture du pair), à la fermeture (), votre connexion s'arrête normalement et les ressources sont libérées car vous n'entrez pas dans l'état TIME_WAIT. Linger ne doit pas être utilisé. Mais si votre application de serveur a un processus de supervision détectant les connexions ouvertes inactives inactives pendant une longue période («longue» doit être définie), vous pouvez arrêter cette connexion inactive de votre côté - voir cela comme une sorte de gestion des erreurs - avec un arrêt avorté. Cela se fait en définissant le délai d'attente à 0. close () enverra alors un RST au client, lui disant que vous êtes en colère :-)


1

Dans les serveurs, vous souhaiterez peut-être envoyer RSTau lieu de FINdéconnecter des clients qui se comportent mal. Cela saute FIN-WAITsuivi des TIME-WAITétats de socket dans le serveur, ce qui empêche d'épuiser les ressources du serveur et, par conséquent, protège de ce type d'attaque par déni de service.


0

J'aime l'observation de Maxim selon laquelle les attaques DOS peuvent épuiser les ressources du serveur. Cela se produit également sans adversaire réellement malveillant.

Certains serveurs doivent faire face à «l'attaque DOS involontaire» qui se produit lorsque l'application cliente a un bogue avec une fuite de connexion, où ils continuent de créer une nouvelle connexion pour chaque nouvelle commande qu'ils envoient à votre serveur. Et puis peut-être finir par fermer leurs connexions s'ils atteignent la pression du GC, ou peut-être que les connexions finissent par expirer.

Un autre scénario est celui où «tous les clients ont la même adresse TCP». Ensuite, les connexions client ne se distinguent que par les numéros de port (si elles se connectent à un seul serveur). Et si les clients commencent à cycler rapidement les connexions d'ouverture / fermeture pour une raison quelconque, ils peuvent épuiser l'espace de tuple (adresse client + port, IP serveur + port).

Je pense donc que les serveurs peuvent être mieux avisés de passer à la stratégie Linger-Zero lorsqu'ils voient un nombre élevé de sockets dans l'état TIME_WAIT - bien que cela ne corrige pas le comportement du client, cela pourrait réduire l'impact.


0

Le socket d'écoute sur un serveur peut utiliser la fonction Linger avec le temps 0 pour accéder immédiatement à la liaison au socket et pour réinitialiser tous les clients dont les connexions ne sont pas encore terminées. TIME_WAIT est quelque chose qui n'est intéressant que lorsque vous avez un réseau à chemins multiples et que vous pouvez vous retrouver avec des paquets mal ordonnés ou si vous avez affaire à un ordre / à l'arrivée de paquets réseau impairs.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.