Quando è richiesta l'opzione TCP SO_LINGER (0)?


95

Penso di capire il significato formale dell'opzione. In alcuni codici legacy che sto gestendo ora, viene utilizzata l'opzione. Il cliente si lamenta di RST come risposta a FIN dal suo lato sulla connessione vicino al suo lato.

Non sono sicuro di poterlo rimuovere in sicurezza, poiché non capisco quando dovrebbe essere usato.

Puoi fare un esempio di quando l'opzione sarebbe richiesta?


1
Dovresti rimuoverlo. Non dovrebbe essere utilizzato nel codice di produzione. L'unica volta che l'ho visto usato è stato il risultato di un benchmark non valido.
Marchese di Lorne

Risposte:


82

Il motivo tipico per impostare un SO_LINGERtimeout pari a zero è evitare un numero elevato di connessioni che si trovano nello TIME_WAITstato, vincolando tutte le risorse disponibili su un server.

Quando una connessione TCP viene chiusa in modo pulito, l'estremità che ha avviato la chiusura ("chiusura attiva") finisce con la connessione in attesa TIME_WAITper diversi minuti. Quindi, se il tuo protocollo è quello in cui il server avvia la chiusura della connessione e coinvolge un numero molto elevato di connessioni di breve durata, potrebbe essere soggetto a questo problema.

Questa non è una buona idea, tuttavia, TIME_WAITesiste per un motivo (per garantire che i pacchetti vaganti da vecchie connessioni non interferiscano con le nuove connessioni). È una buona idea riprogettare il protocollo in uno in cui il client avvia la chiusura della connessione, se possibile.


3
Sono totalmente d'accordo. Ho visto un'applicazione di monitoraggio che stava avviando molte (poche migliaia di connessioni di breve durata ogni X secondi), ed era probabile che diventasse più grande (mille connessioni in più). Non so perché, ma l'applicazionein non rispondeva. Qualcuno ha suggerito SO_LINGER = true, TIME_WAIT = 0 per liberare rapidamente le risorse del sistema operativo e dopo una breve indagine abbiamo provato questa soluzione con ottimi risultati. TIME_WAIT non è più un problema per questa app.
bartosz.r

24
Non sono d'accordo. Un protocollo a livello di applicazione posto sopra il TCP dovrebbe essere progettato in modo tale che il client inizi sempre la chiusura della connessione. In questo modo, TIME_WAITsi siederà al cliente senza fare alcun male. Ricorda come dice "UNIX Network Programming" terza edizione (Stevens et al) a pagina 203: "Lo stato TIME_WAIT è tuo amico ed è lì per aiutarci. Invece di cercare di evitare lo stato, dovremmo capirlo (Sezione 2.7) . "
mgd

8
E se un client desidera aprire 4000 connessioni ogni 30 secondi (questa applicazione di monitoraggio è un client! Perché avvia la connessione)? Sì, possiamo ridisegnare l'applicazione, aggiungere alcuni agenti locali nell'infrastruttura, modificare il modello per eseguire il push. Ma se abbiamo già un'applicazione del genere e cresce, allora possiamo farla funzionare ottimizzando twe indugiare. Si cambia un parametro e all'improvviso si ha un'applicazione funzionante, senza investire un budget per implementare la nuova architettura.
bartosz.r

3
@ bartosz.r: Dico solo che l'uso di SO_LINGER con timeout 0 dovrebbe essere davvero l'ultima risorsa. Ancora una volta, nella terza edizione di "UNIX Network Programming" (Stevens et al) a pagina 203 si dice anche che si rischia la corruzione dei dati. Considera la lettura dell'RFC 1337 dove puoi capire perché TIME_WAIT è tuo amico.
mgd

7
@caf No, la soluzione classica sarebbe un pool di connessioni, come si vede in ogni API TCP pesante, ad esempio HTTP 1.1.
Marchese di Lorne

188

Per il mio suggerimento, leggi l'ultima sezione: "Quando utilizzare SO_LINGER con timeout 0" .

Prima di arrivare a questo, una piccola conferenza su:

  • Terminazione TCP normale
  • TIME_WAIT
  • FIN, ACKeRST

Terminazione TCP normale

La normale sequenza di terminazione TCP è simile a questa (semplificata):

Abbiamo due pari: A e B

  1. Una chiamata close()
    • A invia FINa B
    • A entra in FIN_WAIT_1stato
  2. B riceve FIN
    • B invia ACKad A
    • B entra nello CLOSE_WAITstato
  3. A riceve ACK
    • A entra in FIN_WAIT_2stato
  4. B chiama close()
    • B invia FINad A
    • B entra nello LAST_ACKstato
  5. A riceve FIN
    • A invia ACKa B
    • A entra in TIME_WAITstato
  6. B riceve ACK
    • B passa allo CLOSEDstato, ovvero viene rimosso dalle tabelle dei socket

TEMPO DI ATTESA

Quindi il peer che avvia la terminazione, ovvero chiama per close()primo, finirà nello TIME_WAITstato.

Per capire perché lo TIME_WAITStato è nostro amico, leggere la sezione 2.7 nella terza edizione "Programmazione di rete UNIX" di Stevens et al (pagina 43).

Tuttavia, può essere un problema con molti socket in TIME_WAIT stato su un server in quanto potrebbe impedire l'accettazione di nuove connessioni.

Per ovviare a questo problema, ho visto molti suggerire di impostare l'opzione socket SO_LINGER con timeout 0 prima di chiamare close(). Tuttavia, questa è una cattiva soluzione poiché causa la chiusura della connessione TCP con un errore.

Progettare invece il protocollo dell'applicazione in modo che la terminazione della connessione venga sempre avviata dal lato client. Se il client sa sempre quando ha letto tutti i dati rimanenti, può avviare la sequenza di terminazione. Ad esempio, un browser sa dall'intestazione Content-LengthHTTP quando ha letto tutti i dati e può avviare la chiusura. (So ​​che in HTTP 1.1 lo manterrà aperto per un po 'per un possibile riutilizzo, quindi lo chiuderà.)

Se il server deve chiudere la connessione, progettare il protocollo dell'applicazione in modo che il server chieda al client di chiamare close().

Quando utilizzare SO_LINGER con timeout 0

Di nuovo, secondo la "Programmazione di rete UNIX", terza edizione, pagina 202-203, l'impostazione SO_LINGERcon timeout 0 prima della chiamata close()farà sì che la normale sequenza di terminazione non venga avviata.

Invece, il peer impostando questa opzione e chiamando close()invierà un RST(ripristino della connessione) che indica una condizione di errore ed è così che verrà percepita dall'altra parte. In genere vedrai errori come "Connessione ripristinata da peer".

Pertanto, nella situazione normale è davvero una cattiva idea impostare SO_LINGERcon timeout 0 prima della chiamata close()- d'ora in poi chiamata chiusura abortiva - in un'applicazione server.

Tuttavia, alcune situazioni lo giustificano comunque:

  • Se il client della tua applicazione server si comporta male (va in timeout, restituisce dati non validi, ecc.), Una chiusura abortita ha senso per evitare di rimanere bloccati CLOSE_WAITo finire nello TIME_WAITstato.
  • Se devi riavviare la tua applicazione server che attualmente ha migliaia di connessioni client potresti considerare di impostare questa opzione socket per evitare migliaia di socket server in entrata TIME_WAIT(quando si chiama close()dal lato server) in quanto ciò potrebbe impedire al server di ottenere le porte disponibili per nuove connessioni client dopo essere stato riavviato.
  • A pagina 202 del libro di cui sopra si dice specificamente: "Ci sono alcune circostanze che giustificano l'utilizzo di questa funzione per inviare una chiusura abortita. Un esempio è un terminal server RS-232, che potrebbe bloccarsi per sempre nel CLOSE_WAITtentativo di fornire dati a un terminale bloccato port, ma reimposterebbe correttamente la porta bloccata se riuscisse RSTa eliminare i dati in sospeso. "

Consiglierei questo lungo articolo che credo dia un'ottima risposta alla tua domanda.


6
TIME_WAITè un amico solo quando non inizia a causare problemi: stackoverflow.com/questions/1803566/...
Pacerier

2
quindi cosa succede se stai scrivendo un server web? come si fa a "dire al cliente di avviare una chiusura"?
Shaun Neal

2
@ ShaunNeal ovviamente non lo fai. Ma un client / browser ben scritto avvierà la chiusura. Se il client non si comporta bene, fortunatamente abbiamo l'assassinio di TIME_WAIT per assicurarci di non rimanere senza descrittori di socket e porte effimere.
mgd

16

Quando indugiare è attivo ma il timeout è zero, lo stack TCP non attende l'invio dei dati in sospeso prima di chiudere la connessione. I dati potrebbero essere persi a causa di ciò, ma impostando indugi in questo modo lo accetti e chiedi che la connessione venga ripristinata immediatamente anziché chiusa con grazia. In questo modo viene inviato un RST anziché il normale FIN.

Grazie a EJP per il suo commento, vedere qui per i dettagli.


1
Lo capisco. quello che sto chiedendo è un esempio "realistico" in cui vorremmo utilizzare l'hard reset.
dimba

5
Ogni volta che vuoi interrompere una connessione; quindi se il tuo protocollo fallisce la convalida e hai un cliente che ti dice sciocchezze all'improvviso interromperesti la connessione con un RST, ecc.
Len Holgate,

5
Stai confondendo un timeout di attesa zero con indugiare fuori. Linger off significa che close () non blocca. Attendere con un timeout positivo significa che close () si blocca fino al timeout. Attendere con un timeout pari a zero causa RST, e questo è ciò di cui si tratta.
Marchese di Lorne

2
Sì, hai ragione. Adatterò la risposta per correggere la mia terminologia.
Len Holgate

6

La possibilità o meno di rimuovere la permanenza nel codice dipende dal tipo di applicazione: è un "client" (che apre le connessioni TCP e lo chiude attivamente per primo) o è un "server" (che ascolta un TCP aperto e chiudendolo dopo che l'altro lato ha avviato la chiusura)?

Se la tua applicazione ha il sapore di un "client" (chiusura per prima) E avvii e chiudi un numero enorme di connessioni a server diversi (ad es. Quando la tua app è un'app di monitoraggio che controlla la raggiungibilità di un numero enorme di server diversi) ha il problema che tutte le connessioni client sono bloccate nello stato TIME_WAIT. Quindi, consiglierei di abbreviare il timeout a un valore inferiore a quello predefinito per arrestare comunque in modo corretto ma liberare prima le risorse delle connessioni client. Non imposterei il timeout su 0, poiché 0 non si chiude correttamente con FIN ma si interrompe con RST.

Se la tua applicazione ha il sapore di un "client" e deve recuperare un'enorme quantità di piccoli file dallo stesso server, non dovresti avviare una nuova connessione TCP per file e finire in una quantità enorme di connessioni client in TIME_WAIT, ma mantieni la connessione aperta e recupera tutti i dati sulla stessa connessione. L'opzione Linger può e deve essere rimossa.

Se la tua applicazione è un "server" (chiudi per secondo come reazione alla chiusura del peer), su close () la tua connessione viene interrotta normalmente e le risorse vengono liberate se non entri nello stato TIME_WAIT. Linger non dovrebbe essere usato. Ma se la tua app server ha un processo di supervisione che rileva le connessioni aperte inattive inattive per un lungo periodo ("lungo" deve essere definito) puoi chiudere questa connessione inattiva dal tuo lato - vederla come una sorta di gestione degli errori - con un arresto abortito. Questo viene fatto impostando il timeout di linger a 0. close () invierà quindi un RST al client, dicendogli che sei arrabbiato :-)


1

Nei server, potresti voler inviare RSTinvece di FINquando disconnetti i client che si comportano male. Ciò salta FIN-WAITseguito dagli TIME-WAITstati del socket nel server, che impedisce l'esaurimento delle risorse del server e, quindi, protegge da questo tipo di attacco denial-of-service.


0

Mi piace l'osservazione di Maxim che gli attacchi DOS possono esaurire le risorse del server. Succede anche senza un avversario effettivamente dannoso.

Alcuni server devono affrontare l '"attacco DOS involontario" che si verifica quando l'app client presenta un bug con perdita di connessione, in cui continuano a creare una nuova connessione per ogni nuovo comando che inviano al tuo server. E poi forse alla fine chiudono le loro connessioni se colpiscono la pressione del GC, o forse le connessioni alla fine vanno in timeout.

Un altro scenario è quando "tutti i client hanno lo stesso indirizzo TCP". Quindi le connessioni client sono distinguibili solo dai numeri di porta (se si connettono a un singolo server). E se i client iniziano a ciclare rapidamente aprendo / chiudendo le connessioni per qualsiasi motivo, possono esaurire lo spazio tupla (indirizzo client + porta, IP server + porta).

Quindi penso che i server possano essere consigliati al meglio per passare alla strategia Linger-Zero quando vedono un numero elevato di socket nello stato TIME_WAIT - sebbene non risolva il comportamento del client, potrebbe ridurre l'impatto.


0

Il socket di ascolto su un server può utilizzare indugiare con tempo 0 per avere accesso al binding di nuovo al socket immediatamente e per reimpostare tutti i client le cui connessioni non sono ancora terminate. TIME_WAIT è qualcosa che è interessante solo quando si dispone di una rete a più percorsi e si può finire con pacchetti ordinati in modo errato o altrimenti si ha a che fare con l'ordine / tempi di arrivo dei pacchetti di rete dispari.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.