Table of Contents
Analizzare problemi di banda con Wireshark
Intro
In questo articolo analizzeremo il download di un file corposo tramite Wireshark; sarà un'occasione per affrontare alcuni aspetti della comunicazione TCP tra cui:
- SEQ e ACK numbers
- Selective Acknowledgement (SACK)
- Duplicate ACKs (Dup ACK)
- TCP Retransmission e Fast Retransmission
- Initial Round Trip Time (iRTT)
- Outstanding bytes (o bytes in flight)
- Bandwidth Delay Product (BDP)
- … e altro ancora
La connessione inizia facendo un download dell'ISO della nuova release di debian da https://www.debian.org/distrib/, scegliendo la small installation a 64-bit.
Si tratta di 334 MB di file; dal browser si vede come il download parte relativamente veloce, a 10 MB/s, per poi diminuire progressivamente, fino a stabilizzarsi a 7.4 MB/s.
Analisi del traffico
Conversations
Come prima cosa conviene filtrare la conversazione di rete pertinente. E' sufficiente quindi andare in Statistics - Conversations, selezionare la tab IPv4 e ordinare per Bytes decrescenti; la prima riga è quella che ci interessa (la differenza rispetto alle dimensioni del file scaricato è dovuta agli header):
Filtriamo quindi su di essa con tasto dx e Apply as a filter - Selected - A↔B e poi chiudiamo la finestra; sulla finestra principale di Wireshark compariranno solo i pacchetti appena filtrati.
Verificare il Throughput
Capture file properties
Per avere un'idea precisa del throughput rilevato si può andare anche in Statistics - Capture file properties; poi sotto Statistics ci sono i valori in MB/s o Mb/s (guardare sotto la colonna 'Displayed'). Il valore restituito è la media.
Throughput
Esiste anche un menu opportunamente chiamato Throughput, sotto Statistics - TCP Stream Graphs - Throughput:
Qui si vede l'andamento del throughput nel tempo, con l'evidenza di alcuni rallentamenti all'inizio.
Grafico con tcptrace
Poi conviene farsi un'idea visiva della comunicazione e andare quindi in Statistics - TCP Stream Graphs - Time Sequence (tcptrace) (questo tool è stato introdotto qui):
Si può vedere come il download dura circa 46 s; dividendo le dimensioni del file per questa durata si ottiene:
334 MB / 46 s = 7.2 MB/s
che corrisponde grossomodo alla banda segnalata dal browser durante il download. Inoltre nella curva si notano alcuni glitch in corrispondenza dei secondi 4, 9, 24, 30 e 38.
Effettuando quindi uno zoom su alcune zone del grafico si possono estrapolare le seguenti informazioni.
Slow Start
All'inizio si nota quello che viene definito come slow start
Una spiegazione di questo comportamento iniziale, perfettamente normale ed in linea con gli RFC, si può trovare qui. Fino ad adesso tutto ok.
Delayed ACK
E' evidente poi un pattern; il server invia spesso due pacchetti (soprattutto da 1440 o 2880 bytes) e il client ne fa l'ACK.
Anche questo comportamento è standard durante questo tipo di comunicazione (download):
- invierà un ACK solo dopo aver ricevuto due interi segmenti MSS
- comunque non aspetterà più di un intervallo solitamente a 100 ms o 500 ms, prima di essere obbligato ad inviare un ACK, anche se in presenza di un solo segmento ricevuto
Nel trace ci sono degli ACK anche dopo un solo segmento ricevuto, ma questo è di dimensione 2880 oppure 4320 byte, quindi in effetti si tratta già di due MSS.
Glitch
Ingrandendo il grafico in corrispondenza dei glitch evidenziati prima si vede quanto segue:
Sembrano non promettere nulla di buono…
'TCP Previous segment non captured'
Infatti, dopo circa 3,5 secondi si nota questo comportamento diverso e qui è dove cominciano i problemi. Dal server riceviamo il pacchetto 44841, che però segnala 'TCP Previous segment non captured'; infatti nel grafico si nota un gap:
'TCP Dup ACK'
Il client segnala la cosa nel pacchetto 44842 facendo l'ACK dell'ultimo pacchetto correttamente ricevuto, quello con finale '3603'. Il pacchetto viene marchiato da Wireshark come 'TCP Dup ACK'. Inoltre, andando nei packet details, nelle Options del TCP compare un SACK block del client:
Nel grafico del tcptrace i Dup ACK sono le tacchette sulla riga gialla; mentre il SACK block è rappresentato dalle barrette rosse.
In una battuta, con il pacchetto 44842 il client sta segnalando quanto segue:
“Hey server, ho ricevuto un pacchetto da te, ma quello di prima mi manca. Ti faccio quindi nuovamente l'acknowledge dell'ultimo correttamente ricevuto (il '3603'), così che tu possa spedirmi il pacchetto mancante. In più l'ultimo pacchetto lo tengo nel mio receiver buffer, affinchè tu non debba rispedirmelo. Solo una volta che mi manderai il pacchetto mancante ti farò l'acknowledge anche di quest'ultimo”. Tu, per favore, registra questi SACKed blocks nel tuo sender buffer, così non sarai obbligato a rispedirmeli.
Da come è stato detto, si può capire che il receiver salva i pacchetti oltre il confine dell'acknowledgement, che il sender gli invia, in un buffer, pronti per essere spediti appena gli arriva il pacchetto intermedio mancante:
Dall'immagine sopra si può constatare come il receiver non possa effettuare un ACK dei new data, fino a che il sender non ha inviato la parte in grigio che, per qualche motivo, il receiver non ha ricevuto. Ma il receiver memorizza ugualmente i new data nel suo buffer, pronto per effettuarne l'ACK una volta che il sender gli abbia ritrasmesso il pacchetto mancante.
Il server non invia ancora il pacchetto '3603', ma invece continua ad inviare successivi pacchetti. Il client non può fare altro che segnalargli nuovamente un 'Dup ACK' e ingrandire il SACK block per accomodare il nuovo pacchetto (con uno shift verso destra del right edge del SACK):
Siccome il server continua a non inviare il pacchetto '3603', mentre ne invia altri, il client non può far altro che continuare ad ingrandire il SACK block, fino a che succede che manca un altro pacchetto dal server, segnalato in Wireshark da un nuovo 'TCP Previous segment non captured', così rappresentato nel grafico (notare il nuovo gap):
Il client accomoda il nuovo segmento ricevuto, successivo a quello perso, nel suo buffer. Non sposta il right edge del SACK block, ma ne crea un altro. Il SACK cambia quindi così:
Poi ci sarà un nuovo segmento perso e il client sarà costretto a far posto al successivo così. I SACK block diventano tre:
Nel caso in cui venisse segnalato un nuovo segmento perso, il SACK block più vecchio ('19905043-19915123') scomparirebbe dalle TCP Options, per fargli posto, ma rimarrebbe nel buffer del receiver. L'immagine sottostante mostra i segmenti verticali rossi che corrispondono ai SACK block, mentre un pacchetto perso corrisponde ad un vuoto tra i segmenti. I SACK block più vecchi (cioè quelli più in basso) scompaiono per far posto a quelli più nuovi:
Una conseguenza dei pacchetti persi sono i cosiddetti packets in-flight, cioè pacchetti non ancora acknowledgeati; questi aumenteranno in corrispondenza di Dup ACK per poi azzerarsi una volta che il receiver riesce a ricevere dal sender pacchetti che non ha ricevuto e a fare ACK dei byte che ha nel suo buffer.
'TCP Fast Retransmission'
Adesso che abbiamo capito come funziona il Selective Acknowledgement, c'è da capire perchè il server non si attivi dopo aver ricevuto quattro messaggi di Dup Ack uguali, effettuando delle TCP Fast Retransmission dei byte che mancano al client. Se settiamo un time reference sul quarto pacchetto di Dup ACK (44841) e verifichiamo il tempo trascorso fino a quando (47047) il server non effettua la prima Fast Retransmission del famoso pacchetto '3603', possiamo constatare che sono passati circa 205 ms (colonna 'Time'):
Il client non fa l'acknowledge di '19905043', ma di '19915123'; come si è visto prima, il blocco SACK '19905043-19915123' è stato presente solo temporaneamente nelle TCP Options, ma è stato comunque salvato nel buffer del client. Quindi quest'ultimo, una volta che il server ha spedito il pacchetto mancante, è riuscito a fare l'acknowledge di tutti i byte fino a '19915123'.
Vediamo adesso di capire perchè il server abbia risposto con apparente ritardo.
Ispezionando la 3-way handshake, si nota come il server reagisca alla SYN del client inviando un SYN-ACK in 222 ms; questo valore corrisponde al RTT (Round Trip Time), che è congruo, considerato che il server si trova in Svezia. Quindi, in generale, il server non potrà mai rispondere alle richieste del client prima di circa 200 ms; si capisce allora come il server abbia ricevuto il quarto Dup ACK dal client solo dopo 100 ms (metà RTT) e abbia potuto inviare la Fast Retransmission dopo altri 100 ms, e non prima. Alla base c'è quindi un problema di latenza.
Bandwidth Delay Product (BDP)
Il Bandwidth Delay Product (BDP) è un valore della rete dato da:
Banda x Latenza = BDP
In questo caso, assumendo che il server abbia una banda superiore, il limite è dato dalla banda del client, che è pari a 62Mbps.
Calcoliamo quindi il BDP della rete:
BDP = 62 Mb/s x 0.222 s = 13.764 Mb = 1.72 MB = 1800000 bytes (circa)
Dal trace si nota come la Windows size del receiver all'inizio è piccola per poi aumentare fino a 3145728 byte, quindi sta sfruttando correttamente la capacità della rete data dal BDP.
Window Scaling
La Window size è un valore presente nell'header TCP a 16 bit, che può quindi raggiungere un valore massimo di 65535 byte. Per superare questo limite il client può usare la TCP option Window Scale, che deve essere specificata nel SYN iniziale della connessione. Si tratta di un moltiplicatore da applicare alla Window size. In questo caso il client ha fatto un advertisement iniziale di una Window Scale da 128, quindi il valore effettivo corrisponde alla Calculated Window Size:
E' possibile visualizzare un grafico focalizzato esclusivamente sull'andamento del window scaling andando in Statistics - TCP Stream Graphs - Windows scaling. Come si vede qui di seguito, all'inizio la window size si abbassa fino ad arrivare a zero dopo pochi secondi, per starci fino a quasi 40 secondi:
Disabilitare SACK (solo per prova)
Per comprendere il valore effettivo del SACK (Selective Acknowledgement) si può provare, per test, a disabilitarlo. Su Linux si fa in questo modo:
sysctl -w net.ipv4.tcp_sack=0
E' un'impostazione volatile, che al successivo riavvio non permane.
Già dal grafico tcptrace si nota un comportamento abnorme; il download ha impiegato 6 volte tanto (240 s) rispetto a prima e si notano avvallamenti, indici di un download a singhiozzi:
Zoomando si verificano altri dettagli:
Nello zoom sopra, si notano i pacchetti spediti dal server a sinistra (verso i 3 secondi) e, dopo un intervallo, gli stessi pacchetti ritrasmessi più a destra (verso i 3,6 secondi), non essendo attivo il meccanismo del SACK.
Il primo pacchetto perso dal client è il 17454, che lo segnala al server come Dup ACK, richiedendone il reinvio. Come spiegato sopra, la richiesta di reinvio (il Dup ACK) avviene segnalando nuovamente l'ACK del precedente pacchetto correttamente ricevuto (Sequence nr. 20321203); ma adesso, con il SACK disabilitato, nelle options il client non tiene più traccia dei pacchetti successivi, anche se continua a salvarli nel suo buffer. Quindi la differenza sembra solo lato server, che, senza SACK, non ha evidenza dei segmenti che il client ha nel suo buffer.
TCP Retransmission
Il server è quindi costretto a rispedire il segmento segnalato dal receiver; risponde dopo circa 100 ms (quando è stata effettuata questa prova di disabilitare il SACK la latenza era migliore rispetto ai test sopra con il SACK attivo) con una TCP Retransmission:
Ciò inizia al segmento 20935, dove il server spedisce gli agognati (dal client) byte 20321203-20322643 (1440 byte). Al segmento 20936 il client, che aveva disperatamente richiesto il pacchetto 20321203, fa l'ACK di 20365843 byte, che corrisponde a quanto aveva nel frattempo memorizzato nel suo buffer. In particolare sembra che nel buffer abbia tenuto i byte fino all'invio da parte del sender del successivo TCP Segment not captured.
Al segmento 20937 il sender fa una nuova TCP retransmission di 20367283-20367283 (1440 byte); il receiver fa l'acknowledge di 20404723, cioè questo segmento, che non aveva ricevuto, più altri che aveva tenuto nel buffer e dei quali non poteva ancora fare l'acknowledgement.
'TCP Spurious Retransmission'
Poi, dal segmento 20939 in poi, ci sono una serie di TCP Spurious Retransmission e TCP Dup ACK. Probabilmente a causa della latenza, anche se il receiver aveva inviato l'acknowledge di 20404723, il sender non se ne avvede ed invia 20367283-20368723 (1440 byte). Si tratta di byte che il receiver aveva già acknowledgeato e per questo vengono segnalati come spurious, nonchè come Dup ACK.
Di questi spurious e relativi Dup ACK ce n'è una sfilza; il client sta segnalando quanto segue:
“Hey server, di questi pacchetti ti ho già effettuato l'acknowledge; me ne serve uno successivo, il 20404723.”
Ma fintantochè queste richieste non arrivano al sender, superando la latenza, questi continuerà nell'invio.
Ad un certo punto, c'è una serie di pacchetti stile botta-risposta; si tratta di comunicazione sincrona, visto la latenza pari al RTT, dove il receiver segnala la mancanza di un sequence number e il sender glielo invia:
Corrisponde a questo tratto del tcptrace (la riga in basso):
Il comportamento qui sopra si ripete per tre volte di fila:
Da notare che il sender invia dei byte tramite le curvette, ma non vengono ricevuti dal receiver, quindi non ne fa l'ACK, e allora il sender è costretto a ritrasmetterli un segmento alla volta, un RTT alla volta, impiegando molto tempo. Infatti:
C'è poi un tratto, dai 75 ai 100 secondi, in cui il download avviene in maniera abbastanza spedita, con una sequenza di 2 SEQ-1 ACK, per poi ripresentare i problemi sopra segnalati.
Conclusioni
Una lezione che possiamo trarre è quindi che: