Analysieren Sie die Dauer von TIME_WAIT aus dem Linux-Quellcode

Analysieren Sie die Dauer von TIME_WAIT aus dem Linux-Quellcode

1. Einleitung

Ich bin immer davon ausgegangen, dass die Dauer eines Sockets im Zustand TIME_WAIT unter Linux etwa 60 Sekunden beträgt. Es gibt tatsächlich Sockets online, deren TIME_WAIT 100 s überschreitet. Denn es handelt sich hierbei um die Analyse eines komplexen Fehlers, der erst kürzlich aufgetreten ist. Daher hat der Autor den Linux-Quellcode untersucht, um dies herauszufinden.

2. Lassen Sie uns zunächst die Linux-Umgebung vorstellen

Der Parameter TIME_WAIT bezieht sich normalerweise auf die fünffache Wiederverwendung. Hier gibt der Autor zunächst die Kernelparametereinstellungen der Maschine an, um Verwechslungen mit anderen Problemen zu vermeiden.

Katze /proc/sys/net/ipv4/tcp_tw_reuse 0

Katze /proc/sys/net/ipv4/tcp_tw_recycle 0

Katze /proc/sys/net/ipv4/tcp_timestamps 1

Wie Sie sehen, setzen wir tcp_tw_recycle auf 0, wodurch das Problem vermieden werden kann, das durch die gleichzeitige Aktivierung von tcp_tw_recycle und tcp_timestamps unter NAT verursacht wird.

3. TIME_WAIT-Zustandsübergangsdiagramm

Wenn über den TIME_WAIT-Status eines Sockets gesprochen wird, muss das TCP-Statusübergangsdiagramm angezeigt werden:

Die Dauer beträgt 2 MSL, wie in der Abbildung gezeigt. Die Abbildung gibt jedoch nicht an, wie lang 2MSL ist, aber der Autor hat die folgende Makrodefinition im Linux-Quellcode gefunden.

#define TCP_TIMEWAIT_LEN (60*HZ) /* wie lange soll gewartet werden, um TIME-WAIT zu zerstören
				  * Zustand, ca. 60 Sekunden */

Wie die englische Formulierung wörtlich bedeutet, wird der TIME_WAIT-Zustand nach 60 Sekunden zerstört, also muss 2MSL 60 Sekunden betragen, richtig?

4. Ist die Dauer wirklich wie durch TCP_TIMEWAIT_LEN definiert?

Der Autor war immer der Meinung, dass der Socket im TIME_WAIT-Zustand von 60 Sekunden vom Kernel recycelt werden kann. Sogar der Autor hat selbst ein Experiment durchgeführt, indem er eine Portnummer per Telnet kontaktierte, TIME_WAIT künstlich erstellte und die Zeit selbst maß. Das Experiment konnte in etwa 60 Sekunden wiederholt werden.

Bei der Problemsuche habe ich allerdings festgestellt, dass TIME_WAIT teilweise bis zu 111s dauern kann, anders kann ich mir das Phänomen überhaupt nicht erklären. Dies zwang den Autor, seine eigene Schlussfolgerung zu verwerfen und den Kernel-Quellcode für die TIME_WAIT-Statusverarbeitung erneut zu lesen. Natürlich wird diese Untersuchung auch in einem Blog niedergeschrieben und geteilt, also bleiben Sie dran.

5. TIME_WAIT Timer-Quellcode

Wenn wir darüber sprechen, wann TIME_WAIT recycelt werden kann, müssen wir über den TIME_WAIT-Timer sprechen, der speziell zum Zerstören abgelaufener TIME_WAIT-Sockets verwendet wird. Wenn jeder Socket in den Zustand TIME_WAIT eintritt, durchläuft er zwangsläufig den folgenden Codezweig:

tcp_v4_rcv

|->TCP_ZeitWartezustand_Prozess

/* Verknüpfen Sie den Socket im Zustand time_wait mit dem Zeitrad

|->inet_twsk_schedule

Da unser Kernel tcp_tw_recycle nicht aktiviert, lautet der letzte Aufruf:

/* TCP_TIMEWAIT_LEN 60 * HZ hier */
inet_twsk_schedule(tw, &tcp_death_row, TCP_TIMEWAIT_LEN,
					 TCP_TIMEWAIT_LEN);

Okay, drücken wir diese Kernfunktion.

5.1, inet_twsk_schedule

Bevor Sie den Quellcode lesen, werfen Sie einen Blick auf den allgemeinen Verarbeitungsablauf. Der Linux-Kernel verwendet das Zeitrad, um abgelaufene TIME_WAIT-Sockets zu verarbeiten, wie in der folgenden Abbildung dargestellt:

Der Kernel unterteilt die 60-Sekunden-Zeit in 8 Slots (INET_TWDR_RECYCLE_SLOTS) und jeder Slot verarbeitet den Bereich von 7,5 (60/8) Sockets im Time_Wait-Zustand.

void inet_twsk_schedule(Struktur inet_timewait_sock *tw,Struktur inet_timewait_death_row *twdr,const int timeo,const int timewait_len)
{
	......
	// Berechnen Sie den Schlitz des Zeitrads
	Steckplatz = (Zeit + (1 << INET_TWDR_RECYCLE_TICK) - 1) >> INET_TWDR_RECYCLE_TICK;
	......
	// Logik des langsamen Zeitrads. Da TCP\_TW\_RECYCLE nicht aktiviert ist, beträgt timeo immer 60*HZ (60s)
	// Alle folgen der slow_timer-Logik if (slot >= INET_TWDR_RECYCLE_SLOTS) {
		/* Zeitplan zum Verlangsamen des Timers */
		if (timeo >= Wartezeit) {
			Steckplatz = INET_TWDR_TWKILL_SLOTS – 1;
		} anders {
			Schlitz = DIV_ROUND_UP(Zeitraum, Zeit->Zeitraum);
			wenn (Slot >= INET_TWDR_TWKILL_SLOTS)
				Steckplatz = INET_TWDR_TWKILL_SLOTS – 1;
		}
		tw->tw_ttd = jiffies + zeito;
		// twdr->slot ist der Slot, der aktuell verarbeitet wird
		// Unter TIME_WAIT_LEN ist diese Logik im Allgemeinen 7
		Steckplatz = (twdr->Steckplatz + Steckplatz) & (INET_TWDR_TWKILL_SLOTS - 1);
		Liste = &twdr->Zellen[Slot];
	} anders{
		// Den kurzen Timer ausführen. Aus Platzgründen werde ich hier nicht ins Detail gehen......
	}
	......
	/* twdr->Periode 60/8=7,5 */
	wenn (twdr->tw_count++ == 0)
		mod_timer(&twdr->tw_timer, Augenblicke + twdr->Periode);
	spin_unlock(&twdr->death_lock);
}

Wie aus dem Quellcode ersichtlich ist, beträgt das von uns übergebene Timeout TCP_TIMEWAIT_LEN. Daher wird jedes Mal, wenn ein Socket, der gerade in den Zustand TIME_WAIT gewechselt ist, zur Verarbeitung mit dem Slot verknüpft, der am weitesten vom aktuellen Verarbeitungsslot (+7) entfernt ist. Wie in der folgenden Abbildung dargestellt:

Wenn der Kernel weiterhin TIME_WAIT generiert, sieht das gesamte langsame Timerrad wie in der folgenden Abbildung aus:

Alle Steckplätze sind mit Sockets im Zustand TIME_WAIT gefüllt.

5.2. Spezifische Bereinigungsfunktion

Die Verarbeitungsfunktion, die bei jedem Aufruf von inet_twsk_schedule übergeben wird, ist:

/*Die tcp_death_row im Parameter ist die Struktur, die die Zeitrad-Verarbeitungsfunktion trägt*/
inet_twsk_schedule(tw,&tcp_death_row,TCP_TIMEWAIT_LEN,TCP_TIMEWAIT_LEN)
/* Spezifische Verarbeitungsstruktur */
Struktur inet_timewait_death_row tcp_death_row = {
	......
	/* slow_timer Zeitrad-Verarbeitungsfunktion*/
	.tw_timer = TIMER_INITIALIZER(inet_twdr_hangman, 0,
					    (vorzeichenlos lang)&tcp_death_row),
	/* slow_timer Zeitrad-Hilfsverarbeitungsfunktion*/
	.twkill_work = __WORK_INITIALIZER(tcp_death_row.twkill_work,
					     inet_twdr_twkill_work),
	/* Kurzzeit-Radverarbeitungsfunktion*/
	.twcal_timer = TIMER_INITIALIZER(inet_twdr_twcal_tick, 0,
					    (vorzeichenlos lang)&tcp_death_row),				
};

Da wir uns hauptsächlich mit der auf TCP_TIMEWAIT_LEN (60 s) eingestellten Verarbeitungszeit befassen, untersuchen wir direkt die Zeitrad-Verarbeitungsfunktion slow_timer, d. h. inet_twdr_hangman. Diese Funktion ist relativ kurz:

void inet_twdr_hangman (vorzeichenlose lange Daten)
{
	Struktur inet_timewait_death_row *twdr;
	vorzeichenloser int need_timer;

	twdr = (Struktur inet_timewait_death_row *)Daten;
	spin_lock(&twdr->death_lock);

	wenn (twdr->tw_count == 0)
		gehe raus;

	brauche_timer = 0;
	// Wenn die Anzahl der von diesem Slot verarbeiteten Time_Wait-Sockets 100 erreicht hat und noch nicht verarbeitet wurde, if (inet_twdr_do_twkill_work(twdr, twdr->slot)) {
		twdr->thread_slots |= (1 << twdr->slot);
		// Die verbleibenden Aufgaben zur Verarbeitung an die Arbeitswarteschlange senden schedule_work(&twdr->twkill_work);
		brauche_Timer = 1;
	} anders {
		/* Wir haben den gesamten Slot geleert, ist noch etwas übrig? */
		// Bestimmen Sie, ob die Verarbeitung fortgesetzt werden soll, wenn (twdr->tw_count)
			brauche_Timer = 1;
		// Wenn der aktuelle Slot abgearbeitet ist, springe zum nächsten Slot
		twdr->slot = ((twdr->slot + 1) & (INET_TWDR_TWKILL_SLOTS - 1));
	}
	// Wenn eine weitere Verarbeitung erforderlich ist, führen Sie diese Funktion nach 7,5 s erneut aus, wenn (need_timer)
		mod_timer(&twdr->tw_timer, Augenblicke + twdr->Periode);
aus:
	spin_unlock(&twdr->death_lock);
}

Obwohl diese Funktion einfach ist, steckt sie voller Details. Das erste Detail ist in inet_twdr_do_twkill_work. Um zu verhindern, dass dieser Slot zu viele Time_Waits hat und den aktuellen Prozess blockiert, wird er nach der Verarbeitung von 100 Time_Wait-Sockets zurückgegeben. Die verbleibende Wartezeit dieses Slots wird vom Work_Queue-Mechanismus des Kernels gehandhabt.

Es ist erwähnenswert. Da im slow_timer Zeitrad die genaue Zeit nicht ermittelt wird, werden alle direkt gelöscht. Wenn also ein bestimmter Slot an der Reihe ist, beispielsweise der Slot von 52,5–60 s, werden alle Wartezeiten von 52,5–60 s direkt gelöscht. Dies gilt auch, wenn die Wartezeit 60 s nicht erreicht hat. Das kleine Zeitrad (tw_cal) bestimmt die Zeit genau. Aus Platzgründen werde ich es hier nicht im Detail erklären.

Hinweis: Das kleine Zeitrad (tw_cal) wird verwendet, wenn tcp_tw_recycle eingeschaltet ist

5.3. Machen Sie zuerst eine Annahme

Wir gehen davon aus, dass die Daten eines Zeitrades innerhalb eines Slot-Intervalls verarbeitet werden können, also (60/8=7,5). Da das System über eine tcp_tw_max_buckets-Einstellung verfügt, ist diese Annahme bei einer sinnvollen Einstellung immer noch relativ zuverlässig.

Hinweis: Warum muss 60/8 auf eine Dezimalstelle genau sein und nicht auf 7?

Da die eigentliche Berechnung mit 60*HZ erfolgt,

Wenn HZ 1024 beträgt, sollte die Periode 7680 betragen, was bedeutet, dass die Genauigkeit auf ms-Ebene liegt.

Daher müssen die Berechnungen in diesem Artikel auf die Dezimalstelle genau sein.

5.4. Wenn TIME_WAIT <= 100 in einem Slot

Wenn TIME_WAIT eines Slots <= 100 ist, aktiviert unsere Verarbeitungsfunktion die Work_Queue natürlich nicht. Gleichzeitig wird Slot+1 angefügt, damit in der nächsten Periode der nächste Slot abgearbeitet werden kann. Wie in der folgenden Abbildung dargestellt:

5.5. Wenn TIME_WAIT>100 in einem Slot

Wenn TIME_WAIT eines Slots größer als 100 ist, übergibt der Kernel die verbleibenden Aufgaben zur Verarbeitung an die Work_Queue. Dabei bleibt der Slot unverändert! Das heißt, wenn die nächste Periode (7,5 s später) eintritt, wird derselbe Slot verarbeitet. Nach unserer Annahme ist der Slot zu diesem Zeitpunkt bereits abgearbeitet, so dass der Slot alle 7,5 s weitergeschoben wird. Mit anderen Worten: Unter der Annahme, dass Slot 0 am Anfang steht, dauert die tatsächliche Verarbeitung von Slot 1 15 Sekunden!

Unter der Annahme, dass TIME_WAIT jedes Slots > 100 ist, dauert die Verarbeitung jedes Slots 15 Sekunden.

Für diese Situation hat der Autor ein Programm zur Simulation geschrieben.

öffentliche Klasse TimeWaitSimulator {

    öffentliche statische void main(String[] args) {
        Doppeldelta = (60) * 1,0 / 8;

        // 0 bedeutet Beginn der Bereinigung, 1 bedeutet Bereinigung abgeschlossen // Nachdem die Bereinigung abgeschlossen ist, bewegt sich der Slot vorwärts int startPurge = 0;
        Doppelsumme = 0;
        int-Steckplatz = 0;
        während (Slot < 8) {
            wenn (startPurge == 0) {
                Summe += Delta;
                startPurge = 1;
                wenn (Slot == 7) {
                    // Weil davon ausgegangen wird, dass die Bereinigung nach dem Eintritt in die Arbeitswarteschlange schnell erfolgt. // Wenn der Steckplatz also 7 ist, besteht keine Notwendigkeit, auf den letzten Bereinigungsvorgang von 7,5 Sekunden zu warten.
                    System.out.println("slot " + slot + " hat die letzte " + sum erreicht);
                    brechen;
                }
            }
            wenn (startPurge == 1) {
                Summe += Delta;
                startPurge = 0;
                System.out.println("Slot " + "zum nächsten verschieben zum Zeitpunkt " + Summe);
                //Nach der Reinigung sollte der Slot nach vorne verschoben werden slot++;
            }
        }
    }
}

Die Ergebnisse sind unten dargestellt:

Slot wechselt zum nächsten bei Zeit 15.0

Slot wechselt zum nächsten bei Zeit 30.0

Slot wechselt zum nächsten bei Zeit 45.0

Slot wechselt zum nächsten bei Zeit 60.0

Slot wechselt zum nächsten bei Zeit 75.0

Slot wechselt zum nächsten bei Zeit 90.0

Slot wechselt zum nächsten bei Zeit 105.0

Slot 7 hat die letzten 112,5 erreicht

Das heißt, wenn die Verarbeitung den Zeitbereich von 52,5–60 s erreicht, sind tatsächlich bereits 112,5 s vergangen und die Verarbeitung wird vollständig verzögert. Da der Socket (inet_timewait_sock) im Zustand TIME_WAIT jedoch sehr wenig Speicher belegt, hat er keine großen Auswirkungen auf die verfügbaren Ressourcen des Systems. Dies führt jedoch zu einer Falle in der NAT-Umgebung, die auch dem zuvor im Artikel des Autors erwähnten Fehler entspricht.
Wenn die obige Berechnung gemäß der Grafik und der Zeitleiste durchgeführt wird, sollte sie folgendermaßen aussehen:

Das heißt, wenn ein Socket im Zustand TIME_WAIT den aktuellen Slot innerhalb einer Zeitspanne (7,5 s) verarbeiten kann, kann er maximal 112,5 s existieren!

Wenn die Verarbeitung nicht innerhalb von 7,5 Sekunden abgeschlossen ist, muss sich das Reaktionszeitrad um eine oder mehrere Perioden weiterdrehen. Aufgrund der Beschränkung von tcp_tw_max_buckets sollte es jedoch unmöglich sein, solch strenge Bedingungen zu erfüllen.

5.6. PAWS (Schutz vor umbrochenen Sequenzen) erweitert TIME_WAIT

Tatsächlich ist die obige Schlussfolgerung nicht streng genug. Die TIME_WAIT-Zeit kann noch weiter verlängert werden! Schauen Sie sich diesen Quellcode an:

Aufzählung tcp_tw_status
tcp_timewait_state_process(Struktur inet_timewait_sock *tw, Struktur sk_buff *skb,
			   Konstantenstruktur tcphdr *th)
{
	......
	wenn (Pfoten ablehnen)
		NET_INC_STATS_BH(twsk_net(tw), LINUX_MIB_PAWSESTABREJECTED);
		
	wenn (!th->rst) {
		/* In diesem Fall müssen wir den TIMEWAIT-Timer zurücksetzen.
		 *
		 * Wenn es sich um ACKless SYN handelt, kann es sich sowohl um alte Duplikate handeln
		 * und neue gültige SYN mit zufälliger Sequenznummer <rcv_nxt.
		 * Im letzten Fall keine Terminverschiebung vornehmen.
		 */
		/* Wenn ein Paket eintrifft, das die Wraparound-Prüfung nicht besteht, oder ein Bestätigungspaket * wird der Timer auf neue 60 Sekunden zurückgesetzt */
		wenn (paws_reject || th->ack)
			inet_twsk_schedule(tw, &tcp_death_row, TCP_TIMEWAIT_LEN,
					   TCP_TIMEWAIT_LEN);

		/* ACK senden. Beachten Sie, wir setzen den Eimer nicht,
		 * Es wird vom Anrufer freigegeben.
		 */
		/* Sende die ACK, die im aktuellen Wartezustand zurückgegeben werden soll, an das andere Ende */
		TCP_TW_ACK zurückgeben;
	}
	inet_twsk_put(tw);
	/* Beachten Sie, dass das von paws verifizierte Paket tcp_tw_success zurückgibt, sodass das Socket-Fünffach im time_wait-Zustand auch nach dem Drei-Wege-Handshake erfolgreich wiederverwendet werden kann * /
	gibt TCP_TW_SUCCESS zurück;
}

Die obige Logik wird in der folgenden Abbildung dargestellt:

Beachten Sie die Rückgabe TCP_TW_SUCCESS am Ende des Codes. Das Paket, das die PAWS-Prüfung besteht, gibt TCP_TW_SUCCESS zurück, sodass der Socket (Fünffach) im Zustand TIME_WAIT auch nach dem Drei-Wege-Handshake erfolgreich wiederverwendet werden kann!

Oben finden Sie den detaillierten Inhalt der Analyse der Dauer von TIME_WAIT aus dem Linux-Quellcode. Weitere Informationen zur Dauer von TIME_WAIT im Linux-Quellcode finden Sie in den anderen verwandten Artikeln auf 123WORDPRESS.COM!

Das könnte Sie auch interessieren:
  • Lösung für das Problem zu vieler Apache-Time_Wait-Verbindungen
  • Besprechen Sie, wie Sie das Problem übermäßiger TIME_WAIT auf Linux-Servern reduzieren können
  • Detaillierte Erklärung zur Lösung einer großen Anzahl von TIME WAIT in Linux
  • Lösen Sie das Problem, dass time_wait das Schließen des Sockets erzwingt
  • Fehlerbehebung, wenn zu viele TIME_WAIT-Zustände im Server vorhanden sind

<<:  Vor- und Nachteile gängiger MySQL-Speicher-Engines

>>:  Es gibt eine zusätzliche leere Zeile, nachdem die HTML-Seite Include verwendet, um die PHP-Datei zu importieren

Artikel empfehlen

Docker implementiert Container-Portbindung am lokalen Port

Heute bin ich auf ein kleines Problem gestoßen: N...

Analyse des Konfigurationsprozesses der Nginx-HTTP-Integritätsprüfung

Passive Prüfung Mit passiven Integritätsprüfungen...

Problem mit der Parameterübergabe beim Sprung auf HTML-Seite

Die Wirkung ist wie folgt: eine Seite Nach dem Kl...

So migrieren Sie MySQL-Daten richtig nach Oracle

In der MySQL-Datenbank gibt es eine Tabelle Stude...

Detaillierte Erklärung der MySQL information_schema-Datenbank

1. Übersicht Die Datenbank information_schema ist...

So verwenden Sie display:olck/none zum Erstellen einer Menüleiste

Die Auswirkung der Vervollständigung einer Menüle...

So stellen Sie verschiedene Mausformen dar

<a href = "http: //" style = "c...

So ändern Sie die CentOS-Serverzeit auf die Peking-Zeit

1. Ich habe ein VPS- und CentOS-System gekauft un...

So erstellen Sie einen Nginx-Image-Server mit Docker

Vorwort Bei der allgemeinen Entwicklung werden Bi...

Shell-Skript zur Überwachung des MySQL-Master-Slave-Status

Geben Sie ein Shell-Skript unter Linux frei, um d...

So fragen Sie Bilder in einem privaten Register ab oder erhalten sie

Docker fragt Bilder in einem privaten Register ab...