Comment forcer la fermeture d'un socket dans TIME_WAIT?

113

Je lance un programme particulier sur Linux qui plante parfois. Si vous l'ouvrez rapidement après cela, il écoute sur le socket 49201 au lieu de 49200 comme il l'a fait la première fois. netstat révèle que 49200 est dans un état TIME_WAIT.

Existe-t-il un programme que vous pouvez exécuter pour forcer immédiatement ce socket à quitter l'état TIME_WAIT?

Rehan Khwaja
la source
1
Si vous êtes ici en raison de "trop ​​de personnes TIME_WAITsur le serveur" , passez simplement aux trois premières réponses qui évitent la question au lieu de la répondre.
Pacerier

Réponses:

148
/etc/init.d/networking restart

Laissez-moi élaborer. Le protocole de contrôle de transmission (TCP) est conçu pour être un protocole de transmission de données bidirectionnel, ordonné et fiable entre deux points d'extrémité (programmes). Dans ce contexte, le terme fiable signifie qu'il retransmettra les paquets s'il se perd au milieu. Le protocole TCP garantit la fiabilité en renvoyant des paquets d'accusé de réception (ACK) pour un seul paquet ou une plage de paquets reçus de l'homologue.

Ceci est identique pour les signaux de contrôle tels que demande / réponse de terminaison. La RFC 793 définit l'état TIME-WAIT comme suit:

TIME-WAIT - TIME-WAIT - signifie attendre suffisamment de temps pour être sûr que le protocole distant a bien reçu l'accusé de réception de sa demande de fin de connexion.

Voir le diagramme d'état TCP suivant: texte alternatif

TCP est un protocole de communication bidirectionnel. Ainsi, lorsque la connexion est établie, il n'y a pas de différence entre le client et le serveur. En outre, l'un ou l'autre peut appeler la fermeture et les deux homologues doivent se mettre d'accord sur la fermeture pour fermer complètement une connexion TCP établie.

Appelons le premier à appeler les quittes en tant que rapproché actif, et l'autre à scruter le plus proche passif. Lorsque le système de fermeture actif envoie FIN, l'état passe à FIN-WAIT-1. Ensuite, il reçoit un ACK pour le FIN envoyé et l'état passe à FIN-WAIT-2. Une fois qu'il reçoit également FIN du système de rapprochement passif, le mécanisme de rapprochement actif envoie l'ACK au système FIN et l'état passe à TIME-WAIT. Si le système de rapprochement passif n'a pas reçu l'ACK du deuxième FIN, il retransmettra le paquet FIN.

Le RFC 793 définit le délai d'expiration comme étant le double de la durée de vie maximale du segment, ou 2 MSL. Depuis MSL, la durée maximale pendant laquelle un paquet peut errer sur Internet est définie sur 2 minutes, 2MSL sur 4 minutes. Puisqu’il n’ya pas d’ACK à un ACK, le système de fermeture actif ne peut rien faire, mais attendre 4 minutes s’il adhère correctement au protocole TCP / IP, juste au cas où l’émetteur passif n’aurait pas reçu l’ACK sur son FIN (en théorie). .

En réalité, les paquets manquants sont probablement rares, et très rares si tout se passe sur le réseau local ou sur un seul ordinateur.

Pour répondre à la question, comment fermer de force un socket dans TIME_WAIT?, Je vais quand même m'en tenir à ma réponse d'origine:

/etc/init.d/networking restart

En pratique, je le programmerais pour qu'il ignore l'état TIME-WAIT en utilisant l'option SO_REUSEADDR mentionnée par WMR. Que fait exactement SO_REUSEADDR?

Cette option de socket indique au noyau que même si ce port est occupé (dans
l'état TIME_WAIT), continuez et réutilisez-le quand même. S'il est occupé, mais avec un autre état, vous obtiendrez toujours une erreur d'adresse déjà utilisée. C'est utile si votre serveur a été arrêté, puis redémarré immédiatement alors que les sockets sont toujours actifs sur son port. Vous devez savoir que si des données inattendues arrivent, cela peut perturber votre serveur, mais, bien que cela soit possible, cela est peu probable.

Eugene Yokota
la source
8
Excellente réponse, mais pas la réponse correcte à sa question. Le redémarrage de la mise en réseau fonctionnerait, mais il en irait de même pour le redémarrage, de sorte que cela ne peut pas être correct.
Chris Huang-Leaver
3
@Chris Huang-Leaver, la question est "Existe-t-il un programme que vous pouvez exécuter pour forcer immédiatement ce socket à quitter l'état TIME_WAIT?" si le redémarrage pouvait être considéré comme un programme, alors ce serait aussi une bonne réponse. Pourquoi pensez-vous que cela ne peut pas être juste?
Eugene Yokota
8
WMR a la réponse la plus utile (c'est ce que je fais quand je rencontre ce genre de problème). Redémarrer le réseau est trop radical pour être une solution et pourrait prendre plus de temps que simplement attendre le délai. La réponse correcte à sa question est «Non», mais SO ne vous laissera pas taper les réponses à deux lettres :-)
Chris Huang- Quitter
6
oh ok, la prochaine fois qu’un processus s’arrête sur SIGTERM, je vais simplement casser mon ordinateur au lieu de le réparer.
Longpoke
La généralisation de ceci est "redémarrer les services réseau". L'emplacement spécifique étant spécifique à la /etc/init.d/networkingplate-forme (Debian?), La ligne de commande précise sera différente (parfois assez radicalement) des autres systèmes. Je conviens avec d’autres commentateurs que cela peut sembler excessif et perturber manifestement tous les services réseau non liés.
tripleee
51

Je ne sais pas si vous avez le code source de ce programme particulier que vous exécutez, mais si vous pouviez simplement définir SO_REUSEADDR via setsockopt(2)ce qui vous permet de vous lier à la même adresse locale même si le socket est à l'état TIME_WAIT (sauf si socket écoute activement, voir socket(7)).

Pour plus d'informations sur l'état TIME_WAIT, voir la FAQ sur les sockets Unix .

WMR
la source
mais je n'ai pas eu l'erreur déjà liée. Quand j'exécute à nouveau le programme, il écoute en post (123456). Je peux aussi voir que le système affiche TIME_WAIT pour ce port, mais je peux quand même me connecter. Pourquoi?
Jayapal Chandran le
2
Même avec SO_REUSEADDR, il est toujours possible d’obtenir l’erreur "Adresse déjà utilisée". Pour plus de détails, reportez-vous à http://www.harvard.edu/~fine/Tech/addrinuse.html .
Jingguo Yao
@WMR SO_REUSEADDRne "ferme" pas une socket. Cela vous permet simplement de réutiliser ceux qui sont déjà ouverts. Donc, la question est toujours "Comment forcer de force un socket TIME_WAIT?"
Pacerier
C'est la bonne réponse, mais la question n'était pas totalement correcte. Au moins, j'ai résolu mon problème (pas comme redémarrer tout le réseau en coupant toutes les autres connexions).
V-Mark
SO_REUSEADDRlaisserons bind()procéder; mais si vous voulez ensuite écouter cette prise, vous listen()reviendrez EADDRINUSEtout de même. En d'autres termes, cette réponse peut aider le logiciel client utilisant des ports éphémères, mais ne résout pas le problème du logiciel serveur.
Le
33

Autant que je sache, il est impossible de fermer de force le socket en dehors de l'écriture d'un meilleur gestionnaire de signal dans votre programme, mais il existe un fichier / proc qui contrôle la durée du délai d'attente. Le fichier est

/proc/sys/net/ipv4/tcp_tw_recycle

et vous pouvez définir le délai d'attente à 1 seconde en procédant comme suit:

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

Cependant, cette page contient un avertissement sur les problèmes de fiabilité possibles lors de la définition de cette variable.

Il y a aussi un fichier lié

/proc/sys/net/ipv4/tcp_tw_reuse

qui contrôle si les sockets TIME_WAIT peuvent être réutilisés (probablement sans aucun délai).

Incidemment, la documentation du noyau vous avertit de ne changer aucune de ces valeurs sans «conseils / demandes d'experts techniques». Ce que je ne suis pas.

Le programme doit avoir été écrit pour tenter une liaison vers le port 49200, puis incrémenter de 1 si le port est déjà utilisé. Par conséquent, si vous avez le contrôle du code source, vous pouvez modifier ce comportement afin d'attendre quelques secondes et essayer à nouveau sur le même port, au lieu de l'incrémenter.

Leigh Caldwell
la source
pense que les deux autres exemples devraient être s / rw / tw / je voudrais éditer, mais manque assez de rep.
1
Tiré de la documentation du noyau: Attention. Tcp_tw_recycle et tcp_tw_reuse peuvent provoquer des problèmes. Vous ne devez pas activer non plus sans comprendre la topologie du réseau entre les noeuds qui utilisent ou sont utilisés par le noeud sur lequel le paramètre est activé. Les connexions qui passent par des nœuds connaissant les états de connexion TCP, tels que le pare-feu, le NAT ou l'équilibreur de charge peuvent commencer à supprimer des trames en raison de ce paramètre. Le problème devient visible lorsque le nombre de connexions est suffisant.
Le configurer pour qu'il 1fonctionne pour les connexions futures, mais qu'en est-il des connexions actuelles déjà ouvertes?
Pacerier
18

En fait, il existe un moyen de tuer une connexion - killcx . Ils prétendent que cela fonctionne dans n’importe quel état de la connexion (que je n’ai pas vérifié). Vous devez connaître l’interface où la communication a lieu, mais il semble que eth0 soit utilisé par défaut.

UPDATE: une autre solution est le cutter qui vient dans les dépôts de certaines distributions linux.

Akostadinov
la source
3

Une autre option consiste à utiliser l'option SO_LINGER avec un délai d'expiration égal à 0. De cette manière, lorsque vous fermez le socket est forcément fermé, l'envoi d'un RST au lieu d'entrer dans le comportement de fermeture FIN / ACK. Cela évitera l'état TIME_WAIT et sera peut-être plus approprié pour certaines utilisations.


la source
2
Il perd également toutes les données sortantes encore en transit et peut provoquer une erreur à l'autre extrémité. Non recommandé.
user207421
@EJP Un échec précoce est presque toujours le bon appel. La mise en réseau n'est pas fiable et les combats qui ralentiront les choses. Une application bloquée ne peut pas supposer que des données ont été enregistrées en toute sécurité.
Tobu
1
En fait, je le recommanderais tous les jours où l'autre point d'extrémité est une passerelle de bus industrielle embarquée, buggée, qui implémente son propre transport fiable sur la couche d'application sur TCP, où ledit transport empêche la connexion de se fermer à moins qu'il ne reçoive la taxe RST et se remplit ainsi. la limite de connexion sur cette passerelle. Là. Je vous ai donné un exemple très spécifique et très réel qui, malheureusement, nécessite de recourir à de tels piratages.
andyn
@Tobu La mise en réseau n'est pas fiable, mais TCP essaie de l'être, et aggraver la situation ne signifie rien de mieux que de le faire, et laisser TCP faire son travail ne signifie pas «se battre».
user207421
2

Une autre solution serait de disposer d’un logiciel de transfert de proxy ou de port fiable qui écoute sur le port 49200, puis de transférer la connexion vers l’une des instances de votre programme moins fiable utilisant différents ports ... HAPROXY me vient à l’esprit.

Le port sur lequel vous vous connectez est assez élevé. Vous pouvez essayer d’utiliser un inutilisé juste au-dessus de la plage 0-1024. Votre système est moins susceptible d’utiliser un numéro de port inférieur en tant que port éphémère.

Andrew Pate
la source
0

TIME_WAIT est le problème le plus courant dans l’architecture serveur de programmation par socket. Attendre quelques secondes en essayant périodiquement est la meilleure solution. Pour les applications en temps réel, le serveur doit être installé immédiatement. Il existe une option SO_REUSEADDR pour elles.


la source