====== 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//): {{:wireshark-banda:wireshark-banda-conversations.png|}} 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**. La stessa funzione si può richiamare premendo il **secondo pulsante in basso a sinistra**: {{:wireshark-banda:wireshark-capture-file-properties.png|}} === Throughput === Esiste anche un menu opportunamente chiamato **Throughput**, sotto **Statistics - TCP Stream Graphs - Throughput**: {{:wireshark-banda:wireshark-throughput01.png|}} 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 **[[wireshark#tcptrace|qui]]**): {{:wireshark-banda:wireshark-banda-tcptrace00.png}} Un grafico di un download deve avere la rappresentazione di una **retta inclinata che avanza uniformemente**, come quella sopra: in teoria, maggiore il gradiente, maggiore la banda. Se ci fossero degli **appiattimenti** (non è il caso sopra) saremmo in presenza di ritardi nel trasferimento dei dati, fenomeno da indagare 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//** {{:wireshark-banda:wireshark-banda-tcptrace01.png}} Una spiegazione di questo comportamento iniziale, perfettamente normale ed in linea con gli RFC, si può trovare **[[wireshark#tcptrace|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. Per capire perchè sono presenti pacchetti da 2880 byte vedi **[[wireshark#mss|TSO/LRO]]** Anche questo comportamento è standard durante questo tipo di comunicazione (download): [{{:wireshark-banda:wireshark-send-send-ack.png|//Due segmenti da 1440 inviati dal server, un ACK per entrambi dal client//}}] Quanto sopra è l'implementazione dell'RFC 1122, che definisce il **Delayed ACK**; in questo RFC si parte dal presupposto che, se un host riceve dei dati, non è efficiente nè per esso nè per la rete in generale se invia un ACK ad ogni segmento ricevuto, per l'//overhead// che ne deriva. Quindi il receiver: * 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: {{:wireshark-banda:wireshark-dup-ack00.png|}} 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//: [{{:wireshark-banda:wireshark-dup-ack01.png|//Un segmento non ricevuto ("captured") sotto la barra blu verticale e le barre rosse verticali in corrispondenza dei Dup ACK del client//}}] ==== '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: [{{:wireshark-banda:wireshark-dup-ack02.png|//SACK option 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: [{{:wireshark-banda:wireshark-cumulative.png|//Il TCP ragiona secondo un meccanismo **cumulativo**//}}] 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)**: {{:wireshark-banda:wireshark-dup-ack03.png|}} 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//): [{{:wireshark-banda:wireshark-dup-ack04.png|//Un nuovo segmento inviato dal server e marcato come 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ì: [{{:wireshark-banda:wireshark-dup-ack05.png|//Adesso i buffer SACK sono due//}}] Poi ci sarà un nuovo segmento perso e il client sarà costretto a far posto al successivo così. I SACK block diventano tre: {{:wireshark-banda:wireshark-dup-ack06.png|}} L'**RFC** stabilisce che il server debba reinviare il pacchetto segnalato **dopo quattro Dup Ack** (meccanismo della **//Fast Retransmission//**); perchè non lo fa? La spiegazione è più sotto, ma fondamentalmente **dipende dalla latenza** tra client e server 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: [{{:wireshark-banda:wireshark-sack.png|//I segmenti verticali rossi corrispondono ai SACK block, gli spazi vuoti tra i segmenti a pacchetti persi//}}] I SACK block (segmenti verticali rossi) di cui si fa l'//advertisement// possono essere al massimo 3 in un dato istante. Ma il client e il server terranno nota di questi, dei precedenti e dei successivi nei loro, rispettivamente, receiver e sender buffer 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'): {{:wireshark-banda:wireshark-fast-retransmission.png|}} 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'. Quanto sopra fa capire una volta di più come il **TCP** ragioni in ottica di //acknowledgement// **cumulativi** 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) **Il BDP corrisponde alla massima quantità di dati possibile sulla rete in ogni momento e dovrebbe essere minore o uguale al valore della //TCP Windows size// del receiver** 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//: {{:wireshark-banda:wireshark-window-scale.png|}} Il //window scaling// viene stabilito durante la //3-way handshake//, ma la **//calculated window size//** viene usata solo dopo questa fase 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: [{{:wireshark-banda:wireshark-window-scaling-graph.png|//Andamento della receiver window size in verde, mentre i pallini sono i dati inviati. Quando c'è una //zero window condition// non ci sono dati scambiati.//}}] ==== 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: {{:wireshark-banda:wireshark-nosack01.png|//Disabilitando il SACK il download procede a singhiozzi//}} Il download dello stesso file, con il **SACK disabilitato**, ha impiegato **6 volte tanto**! Zoomando si verificano altri **dettagli**: {{:wireshark-banda:wireshark-nosack03.png|//Pacchetti trasmessi dal server, non //acknowledgeati// dal client e ritrasmessi dal server}} 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**: {{:wireshark-banda:wireshark-nosack04.png|}} 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: [{{:wireshark-banda:wireshark-spurious01.png|//Il receiver segnala un sequence number, il server lo invia. Notare le latenze//}}] Corrisponde a questo tratto del tcptrace (la riga in basso): {{:wireshark-banda:wireshark-spurious02.png|}} Il comportamento qui sopra si **ripete per tre volte di fila**: {{:wireshark-banda:wireshark-botta-risposta.png|}} 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: Questo comportamento dura circa **75 secondi!** 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: **In presenza di segnalazioni di //TCP Retransmission// o //TCP Spurious Retransmission// verificare l'abilitazione o meno del SACK nel //3-way handshake//; in caso non lo sia abilitarlo**