Wie wird ein Socket in TIME_WAIT gewaltsam geschlossen?

113

Ich führe ein bestimmtes Programm unter Linux aus, das manchmal abstürzt. Wenn Sie es danach schnell öffnen, hört es den Socket 49201 anstelle von 49200 wie beim ersten Mal ab. netstat gibt an, dass sich 49200 in einem TIME_WAIT-Status befindet.

Gibt es ein Programm, das Sie ausführen können, um sofort zu erzwingen, dass der Socket aus dem TIME_WAIT-Status verschoben wird?

Rehan Khwaja
quelle
1
Wenn Sie aufgrund von "zu vielen TIME_WAITServern" hier sind , überspringen Sie einfach die ersten drei Antworten, um die Frage zu vermeiden, anstatt sie zu beantworten.
Pacerier

Antworten:

148
/etc/init.d/networking restart

Lassen Sie mich näher darauf eingehen. Das Transmission Control Protocol (TCP) ist als bidirektionales, geordnetes und zuverlässiges Datenübertragungsprotokoll zwischen zwei Endpunkten (Programmen) konzipiert. In diesem Zusammenhang bedeutet der Begriff "zuverlässig", dass die Pakete erneut übertragen werden, wenn sie in der Mitte verloren gehen. TCP garantiert die Zuverlässigkeit durch das Zurücksenden von Bestätigungspaketen (Acknowledgement, ACK) für ein einzelnes Paket oder einen Bereich von Paketen, die vom Peer empfangen wurden.

Dies gilt auch für die Steuersignale wie Beendigungsanforderung / -antwort. RFC 793 definiert den Status TIME-WAIT wie folgt:

TIME-WAIT - Stellt das Warten auf genügend Zeit dar, um sicherzustellen, dass das entfernte TCP die Bestätigung seiner Verbindungsanforderung zur Beendigung erhalten hat.

Siehe folgendes TCP-Zustandsdiagramm: Alt-Text

TCP ist ein bidirektionales Kommunikationsprotokoll. Wenn die Verbindung hergestellt wird, gibt es keinen Unterschied zwischen dem Client und dem Server. Beide können auch Quits aufrufen, und beide Peers müssen sich auf das Schließen einigen, um eine hergestellte TCP-Verbindung vollständig zu schließen.

Nennen wir den ersten, der die Quits als aktiven Näher bezeichnet, und den anderen, der den passiven Näher bezeichnet. Wenn der aktive Schließer FIN sendet, geht der Status zu FIN-WAIT-1. Dann erhält es eine ACK für das gesendete FIN und der Zustand geht zu FIN-WAIT-2. Sobald er FIN auch vom passiven Schließer empfängt, sendet der aktive Schließer die ACK an den FIN und der Zustand geht zu TIME-WAIT. Falls der passive Schließer die ACK für die zweite FIN nicht erhalten hat, überträgt er das FIN-Paket erneut.

RFC 793 legt das TIME-OUT auf das Doppelte der maximalen Segmentlebensdauer oder 2MSL fest. Da MSL, die maximale Zeit, die ein Paket im Internet durchwandern kann, auf 2 Minuten festgelegt ist, beträgt 2MSL 4 Minuten. Da es keine ACK für eine ACK gibt, kann der aktive Schließer nichts anderes tun, als 4 Minuten zu warten, wenn er das TCP / IP-Protokoll korrekt einhält, nur für den Fall, dass der passive Absender die ACK für seine FIN nicht erhalten hat (theoretisch) .

In der Realität sind fehlende Pakete wahrscheinlich selten und sehr selten, wenn alles im LAN oder auf einer einzelnen Maschine stattfindet.

Um die Frage wörtlich zu beantworten, wie ein Socket in TIME_WAIT gewaltsam geschlossen wird, halte ich mich weiterhin an meine ursprüngliche Antwort:

/etc/init.d/networking restart

In der Praxis würde ich es so programmieren, dass es den TIME-WAIT-Status mit der SO_REUSEADDR-Option ignoriert, wie in WMR erwähnt. Was genau macht SO_REUSEADDR?

Diese Socket-Option teilt dem Kernel mit, dass selbst wenn dieser Port belegt ist (im
TIME_WAIT-Status), er trotzdem weiterverwendet werden soll. Wenn es besetzt ist, aber mit einem anderen Status, erhalten Sie immer noch einen Fehler, dass die Adresse bereits verwendet wird. Dies ist nützlich, wenn Ihr Server heruntergefahren und dann sofort neu gestartet wurde, während Sockets an seinem Port noch aktiv sind. Sie sollten sich darüber im Klaren sein, dass unerwartete Daten Ihren Server verwirren können. Dies ist zwar möglich, aber unwahrscheinlich.

Eugene Yokota
quelle
8
Tolle Antwort, aber nicht die richtige Antwort auf seine Frage. Ein Neustart des Netzwerks würde funktionieren, aber dann würde es neu starten, sodass dies nicht richtig sein kann.
Chris Huang-Leaver
3
@Chris Huang-Leaver, die Frage ist: "Gibt es ein Programm, das Sie ausführen können, um sofort zu erzwingen, dass der Socket den TIME_WAIT-Status verlässt?" Wenn ein Neustart in Betracht gezogen werden könnte, ein Programm auszuführen, wäre dies ebenfalls eine richtige Antwort. Warum denkst du, kann das nicht richtig sein?
Eugene Yokota
8
WMR hat die nützlichste Antwort (was ich mache, wenn ich auf diese Art von Problem stoße). Ein Neustart des Netzwerks ist zu drastisch, um eine Lösung zu finden, und kann länger dauern, als nur auf das Timeout zu warten. Die richtige Antwort auf seine Frage lautet "Nein", aber Sie können auch keine Zwei-Buchstaben-Antworten
eingeben
6
oh okay, wenn das nächste Mal ein Prozess an SIGTERM hängt, werde ich einfach meinen Computer zerschlagen, anstatt ihn zu reparieren.
Longpoke
Die Verallgemeinerung hierfür lautet "Netzwerkdienste neu starten". Der spezifische Ort /etc/init.d/networkingist plattformspezifisch (Debian?), So dass sich die genaue Befehlszeile für andere Systeme (manchmal radikal) unterscheidet. Ich stimme anderen Kommentatoren zu, dass dies ein schwerer Overkill und offensichtlich störend für alle nicht verwandten Netzwerkdienste zu sein scheint.
Tripleee
51

Ich weiß nicht, ob Sie den Quellcode des bestimmten Programms haben, das Sie ausführen, aber wenn ja, können Sie SO_REUSEADDR festlegen, über setsockopt(2)das Sie dieselbe lokale Adresse binden können, auch wenn sich der Socket im Status TIME_WAIT befindet (sofern dies nicht der Fall ist) Buchse hört aktiv zu, siehe socket(7)).

Weitere Informationen zum Status TIME_WAIT finden Sie in den häufig gestellten Fragen zum Unix-Socket .

WMR
quelle
aber ich habe den bereits gebundenen fehler nicht bekommen. Wenn ich das Programm erneut ausführe, hört es auf Post (123456). Außerdem kann ich sehen, dass das System TIME_WAIT für diesen Port anzeigt, aber ich kann trotzdem eine Verbindung herstellen. Warum?
Jayapal Chandran
2
Selbst mit SO_REUSEADDR ist es immer noch möglich, den Fehler "Adresse wird bereits verwendet" zu erhalten. Einzelheiten finden Sie unter www.harvard.edu/~fine/Tech/addrinuse.html .
Jingguo Yao
@WMR SO_REUSEADDR"schließt" keinen Socket. Sie können nur die bereits geöffneten wiederverwenden. Die Frage ist also immer noch: "Wie kann man eine Steckdose zwangsweise schließen TIME_WAIT?"
Pacerier
Dies ist die richtige Antwort, aber die Frage war nicht ganz richtig. Zumindest mein Problem gut gelöst (nicht wie das gesamte Netzwerk neu zu starten und alle anderen Verbindungen zu unterbrechen).
V-Mark
SO_REUSEADDRwerde bind()weitermachen lassen ; aber wenn du dann auf diese buchse hören willst, kehre egal listen()zurück EADDRINUSE. Mit anderen Worten, diese Antwort hilft möglicherweise Client-Software bei der Verwendung von kurzlebigen Ports, löst jedoch nicht das Problem für Server-Software.
Will
33

Soweit ich weiß, gibt es keine Möglichkeit, den Socket außerhalb des Schreibens eines besseren Signal-Handlers in Ihr Programm zwangsweise zu schließen, aber es gibt eine / proc-Datei, die steuert, wie lange das Timeout dauert. Die Datei ist

/proc/sys/net/ipv4/tcp_tw_recycle

und Sie können das Timeout auf 1 Sekunde einstellen, indem Sie dies tun:

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

Diese Seite enthält jedoch eine Warnung zu möglichen Zuverlässigkeitsproblemen beim Festlegen dieser Variablen.

Es gibt auch eine zugehörige Datei

/proc/sys/net/ipv4/tcp_tw_reuse

Hiermit wird gesteuert, ob TIME_WAIT-Sockets wiederverwendet werden können (vermutlich ohne Timeout).

Im Übrigen warnt Sie die Kerneldokumentation davor, einen dieser Werte ohne "Ratschläge / Anfragen von technischen Experten" zu ändern. Was ich nicht bin

Das Programm muss geschrieben worden sein, um eine Bindung an Port 49200 zu versuchen, und dann um 1 zu erhöhen, wenn der Port bereits verwendet wird. Wenn Sie also die Kontrolle über den Quellcode haben, können Sie dieses Verhalten so ändern, dass Sie einige Sekunden warten und es am gleichen Port erneut versuchen, anstatt ihn zu erhöhen.

Leigh Caldwell
quelle
Ich denke, die zweiten beiden Beispiele sollten s / rw / tw / ich würde bearbeiten, aber es mangelt an genügend Repräsentanten.
1
Entnommen aus der Kerneldokumentation: Achtung. Sowohl tcp_tw_recycle als auch tcp_tw_reuse können Probleme verursachen. Sie sollten entweder nicht aktivieren, ohne die Netzwerktopologie zwischen den Knoten zu verstehen, die von dem Knoten verwendet werden, auf dem der Parameter aktiviert ist. Verbindungen über Knoten, die den TCP-Verbindungsstatus kennen, z. B. Firewall, NAT oder Load Balancer, können aufgrund der Einstellung dazu führen, dass Frames gelöscht werden. Das Problem wird sichtbar, wenn die Anzahl der Verbindungen groß genug ist.
Festlegen, dass es 1für zukünftige Verbindungen funktioniert, aber was ist mit den aktuellen Verbindungen, die bereits geöffnet sind?
Pacerier
18

Tatsächlich gibt es eine Möglichkeit, eine Verbindung zu beenden - killcx . Sie behaupten, es funktioniert in jedem Zustand der Verbindung (die ich nicht überprüft habe). Sie müssen jedoch die Schnittstelle kennen, an der die Kommunikation stattfindet. Sie scheint standardmäßig eth0 anzunehmen.

UPDATE: Eine andere Lösung ist Cutter, die in den Repositories einiger Linux-Distributionen enthalten ist.

akostadinov
quelle
3

Eine andere Option ist die Verwendung der Option SO_LINGER mit einer Zeitüberschreitung von 0. Auf diese Weise wird beim Schließen des Sockets ein RST gesendet, anstatt das FIN / ACK-Schließverhalten zu aktivieren. Dies vermeidet den TIME_WAIT-Status und ist möglicherweise für einige Verwendungszwecke besser geeignet.


quelle
2
Es verliert auch alle ausgehenden Daten, die noch übertragen werden, und kann am anderen Ende einen Fehler verursachen. Nicht empfohlen.
user207421
@EJP Frühes Scheitern ist fast immer der richtige Anruf. Vernetzung ist nicht zuverlässig und Kämpfe, die die Dinge verlangsamen. Eine abgestürzte App kann nicht davon ausgehen, dass Daten sicher erkannt wurden.
Tobu
1
Eigentlich würde ich dies jedem Tag empfehlen, an dem der andere Endpunkt ein fehlerhaftes, eingebettetes Industriebus-Gateway ist, das einen eigenen zuverlässigen Transport auf Anwendungsebene über TCP implementiert, wobei dieser Transport verhindert, dass die Verbindung jemals geschlossen wird, es sei denn, es empfängt RST und füllt sich damit das Verbindungslimit auf diesem Gateway. Dort. Ich habe Ihnen ein sehr konkretes und sehr reales Beispiel gegeben, bei dem leider auf solche Hacks zurückgegriffen werden muss.
andyn
@Tobu Networking ist nicht zuverlässig, aber TCP versucht es zu sein, und das zu verschlimmern bedeutet nichts Besseres, und TCP seinen Job machen zu lassen, bedeutet nichts zu „bekämpfen“.
user207421
2

Eine alternative Lösung wäre, eine zuverlässige Proxy- oder Portweiterleitungssoftware zu haben, die Port 49200 abhört und dann die Verbindung über verschiedene Ports an eine von mehreren Instanzen Ihres weniger zuverlässigen Programms weiterleitet ... HAPROXY ist eine gute Idee.

Übrigens ist der Port, an dem Sie eine Verbindung herstellen, ziemlich hoch. Sie können versuchen, einen nicht verwendeten Wert direkt über dem Bereich 0-1024 zu verwenden. Es ist weniger wahrscheinlich, dass Ihr System eine niedrigere Portnummer als kurzlebigen Port verwendet.

Andrew Pate
quelle
0

TIME_WAIT ist das häufigste Problem bei der Socket-Programmierung der Client-Server-Architektur. Warten Sie einige Sekunden und versuchen Sie es in regelmäßigen Abständen. Für Echtzeitanwendungen, die der Server benötigt, muss er sofort aufstehen. Für sie gibt es die Option SO_REUSEADDR.


quelle