1 /* sctp_graph_dialog.cpp
2  *
3  * Wireshark - Network traffic analyzer
4  * By Gerald Combs <gerald@wireshark.org>
5  * Copyright 1998 Gerald Combs
6  *
7  * SPDX-License-Identifier: GPL-2.0-or-later
8  */
9 
10 #include <wsutil/utf8_entities.h>
11 
12 #include "sctp_graph_dialog.h"
13 #include <ui_sctp_graph_dialog.h>
14 #include "sctp_assoc_analyse_dialog.h"
15 
16 #include <file.h>
17 #include <math.h>
18 #include <epan/dissectors/packet-sctp.h>
19 #include "epan/packet.h"
20 
21 #include "ui/tap-sctp-analysis.h"
22 
23 #include <QMessageBox>
24 
25 #include <ui/qt/utils/qt_ui_utils.h>
26 #include <ui/qt/widgets/qcustomplot.h>
27 #include "ui/qt/widgets/wireshark_file_dialog.h"
28 #include "wireshark_application.h"
29 
SCTPGraphDialog(QWidget * parent,const sctp_assoc_info_t * assoc,capture_file * cf,int dir)30 SCTPGraphDialog::SCTPGraphDialog(QWidget *parent, const sctp_assoc_info_t *assoc,
31         capture_file *cf, int dir) :
32     QDialog(parent),
33     ui(new Ui::SCTPGraphDialog),
34     cap_file_(cf),
35     frame_num(0),
36     direction(dir),
37     relative(false),
38     type(1)
39 {
40     Q_ASSERT(assoc);
41     selected_assoc_id = assoc->assoc_id;
42 
43     ui->setupUi(this);
44     Qt::WindowFlags flags = Qt::Window | Qt::WindowSystemMenuHint
45             | Qt::WindowMinimizeButtonHint
46             | Qt::WindowMaximizeButtonHint
47             | Qt::WindowCloseButtonHint;
48     this->setWindowFlags(flags);
49     this->setWindowTitle(QString(tr("SCTP TSNs and SACKs over Time: %1 Port1 %2 Port2 %3"))
50             .arg(gchar_free_to_qstring(cf_get_display_name(cap_file_))).arg(assoc->port1).arg(assoc->port2));
51     if ((direction == 1 && assoc->n_array_tsn1 == 0) || (direction == 2 && assoc->n_array_tsn2 == 0)) {
52         QMessageBox msgBox;
53         msgBox.setText(tr("No Data Chunks sent"));
54         msgBox.exec();
55         return;
56     } else {
57         drawGraph(assoc);
58     }
59 }
60 
~SCTPGraphDialog()61 SCTPGraphDialog::~SCTPGraphDialog()
62 {
63     delete ui;
64 }
65 
drawNRSACKGraph(const sctp_assoc_info_t * selected_assoc)66 void SCTPGraphDialog::drawNRSACKGraph(const sctp_assoc_info_t* selected_assoc)
67 {
68     tsn_t *sack = Q_NULLPTR;
69     GList *list = Q_NULLPTR, *tlist = Q_NULLPTR;
70     guint16 gap_start=0, gap_end=0, i, numberOf_gaps, numberOf_nr_gaps;
71     guint8 type;
72     guint32 tsnumber, j = 0, min_tsn, rel = 0;
73     struct nr_sack_chunk_header *nr_sack_header = Q_NULLPTR;
74     struct gaps *nr_gap = Q_NULLPTR;
75     /* This holds the sum of gap acks and nr gap acks */
76     guint16 total_gaps = 0;
77 
78     if (direction == 1) {
79         list = g_list_last(selected_assoc->sack1);
80         min_tsn = selected_assoc->min_tsn1;
81     } else {
82         list = g_list_last(selected_assoc->sack2);
83         min_tsn = selected_assoc->min_tsn2;
84     }
85     if (relative) {
86         rel = min_tsn;
87     }
88     while (list) {
89         sack = gxx_list_data(tsn_t*, list);
90         tlist = g_list_first(sack->tsns);
91         while (tlist) {
92             type = gxx_list_data(struct chunk_header *, tlist)->type;
93             if (type == SCTP_NR_SACK_CHUNK_ID) {
94                 nr_sack_header = gxx_list_data(struct nr_sack_chunk_header *, tlist);
95                 numberOf_nr_gaps=g_ntohs(nr_sack_header->nr_of_nr_gaps);
96                 numberOf_gaps=g_ntohs(nr_sack_header->nr_of_gaps);
97                 tsnumber = g_ntohl(nr_sack_header->cum_tsn_ack);
98                 total_gaps = numberOf_gaps + numberOf_nr_gaps;
99                 /* If the number of nr_gaps is greater than 0 */
100                 if (total_gaps > 0) {
101                     nr_gap = &nr_sack_header->gaps[0];
102                     for (i = 0; i < total_gaps; i++) {
103                         gap_start = g_ntohs(nr_gap->start);
104                         gap_end = g_ntohs(nr_gap->end);
105                         for (j = gap_start; j <= gap_end; j++) {
106                             if (i >= numberOf_gaps) {
107                                 yn.append(j + tsnumber - rel);
108                                 xn.append(sack->secs + sack->usecs/1000000.0);
109                                 fn.append(sack->frame_number);
110                             } else {
111                                 yg.append(j + tsnumber - rel);
112                                 xg.append(sack->secs + sack->usecs/1000000.0);
113                                 fg.append(sack->frame_number);
114                             }
115                         }
116                         if (i < total_gaps-1)
117                             nr_gap++;
118                     }
119 
120                     if (tsnumber>=min_tsn) {
121                         ys.append(j + tsnumber - rel);
122                         xs.append(sack->secs + sack->usecs/1000000.0);
123                         fs.append(sack->frame_number);
124                     }
125                 }
126             }
127             tlist = gxx_list_next(tlist);
128         }
129         list = gxx_list_previous(list);
130     }
131 }
132 
drawSACKGraph(const sctp_assoc_info_t * selected_assoc)133 void SCTPGraphDialog::drawSACKGraph(const sctp_assoc_info_t* selected_assoc)
134 {
135     GList *listSACK = Q_NULLPTR, *tlist = Q_NULLPTR;
136     guint16 gap_start=0, gap_end=0, nr, dup_nr;
137     struct sack_chunk_header *sack_header = Q_NULLPTR;
138     struct gaps *gap = Q_NULLPTR;
139     tsn_t *tsn = Q_NULLPTR;
140     guint8 type;
141     guint32 tsnumber=0, rel = 0;
142     guint32 minTSN;
143     guint32 *dup_list = Q_NULLPTR;
144     int i, j;
145 
146     if (direction == 1) {
147         minTSN = selected_assoc->min_tsn1;
148         listSACK = g_list_last(selected_assoc->sack1);
149     } else {
150         minTSN = selected_assoc->min_tsn2;
151         listSACK = g_list_last(selected_assoc->sack2);
152     }
153     if (relative) {
154         rel = minTSN;
155     }
156     while (listSACK) {
157         tsn = gxx_list_data(tsn_t*, listSACK);
158         tlist = g_list_first(tsn->tsns);
159         while (tlist) {
160             type = gxx_list_data(struct chunk_header *, tlist)->type;
161             if (type == SCTP_SACK_CHUNK_ID) {
162                 sack_header = gxx_list_data(struct sack_chunk_header *, tlist);
163                 nr=g_ntohs(sack_header->nr_of_gaps);
164                 tsnumber = g_ntohl(sack_header->cum_tsn_ack);
165                 dup_nr=g_ntohs(sack_header->nr_of_dups);
166                 if (nr>0) {  // Gap Reports green
167                     gap = &sack_header->gaps[0];
168                     for (i=0;i<nr; i++) {
169                         gap_start=g_ntohs(gap->start);
170                         gap_end = g_ntohs(gap->end);
171                         for (j=gap_start; j<=gap_end; j++) {
172                             yg.append(j + tsnumber - rel);
173                             xg.append(tsn->secs + tsn->usecs/1000000.0);
174                             fg.append(tsn->frame_number);
175                         }
176                         if (i < nr-1)
177                             gap++;
178                     }
179                 }
180                 if (tsnumber>=minTSN) { // CumTSNAck red
181                     ys.append(tsnumber - rel);
182                     xs.append(tsn->secs + tsn->usecs/1000000.0);
183                     fs.append(tsn->frame_number);
184                 }
185                 if (dup_nr > 0) { // Duplicates cyan
186                     dup_list = &sack_header->a_rwnd + 2 + nr;
187                     for (i = 0; i < dup_nr; i++) {
188                         tsnumber = g_ntohl(dup_list[i]);
189                         if (tsnumber >= minTSN) {
190                             yd.append(tsnumber - rel);
191                             xd.append(tsn->secs + tsn->usecs/1000000.0);
192                             fd.append(tsn->frame_number);
193                         }
194                     }
195                 }
196             }
197             tlist = gxx_list_next(tlist);
198         }
199         listSACK = gxx_list_previous(listSACK);
200     }
201 
202     QCPScatterStyle myScatter;
203     myScatter.setShape(QCPScatterStyle::ssCircle);
204     myScatter.setSize(3);
205 
206     int graphcount = ui->sctpPlot->graphCount();
207     // create graph and assign data to it:
208 
209     // Add SACK graph
210     if (xs.size() > 0) {
211         QCPGraph *gr = ui->sctpPlot->addGraph();
212         gr->setName(QString("SACK"));
213         myScatter.setPen(QPen(Qt::red));
214         myScatter.setBrush(Qt::red);
215         ui->sctpPlot->graph(graphcount)->setScatterStyle(myScatter);
216         ui->sctpPlot->graph(graphcount)->setLineStyle(QCPGraph::lsNone);
217         ui->sctpPlot->graph(graphcount)->setData(xs, ys);
218         typeStrings.insert(graphcount, QString(tr("CumTSNAck")));
219         graphcount++;
220     }
221 
222     // Add Gap Acks
223     if (xg.size() > 0) {
224         QCPGraph *gr = ui->sctpPlot->addGraph();
225         gr->setName(QString("GAP"));
226         myScatter.setPen(QPen(Qt::green));
227         myScatter.setBrush(Qt::green);
228         ui->sctpPlot->graph(graphcount)->setScatterStyle(myScatter);
229         ui->sctpPlot->graph(graphcount)->setLineStyle(QCPGraph::lsNone);
230         ui->sctpPlot->graph(graphcount)->setData(xg, yg);
231         typeStrings.insert(graphcount, QString(tr("Gap Ack")));
232         graphcount++;
233     }
234 
235     // Add NR Gap Acks
236     if (xn.size() > 0) {
237         QCPGraph *gr = ui->sctpPlot->addGraph();
238         gr->setName(QString("NR_GAP"));
239         myScatter.setPen(QPen(Qt::blue));
240         myScatter.setBrush(Qt::blue);
241         ui->sctpPlot->graph(graphcount)->setScatterStyle(myScatter);
242         ui->sctpPlot->graph(graphcount)->setLineStyle(QCPGraph::lsNone);
243         ui->sctpPlot->graph(graphcount)->setData(xn, yn);
244         typeStrings.insert(graphcount, QString(tr("NR Gap Ack")));
245         graphcount++;
246     }
247 
248     // Add Duplicates
249     if (xd.size() > 0) {
250         QCPGraph *gr = ui->sctpPlot->addGraph();
251         gr->setName(QString("DUP"));
252         myScatter.setPen(QPen(Qt::cyan));
253         myScatter.setBrush(Qt::cyan);
254         ui->sctpPlot->graph(graphcount)->setScatterStyle(myScatter);
255         ui->sctpPlot->graph(graphcount)->setLineStyle(QCPGraph::lsNone);
256         ui->sctpPlot->graph(graphcount)->setData(xd, yd);
257         typeStrings.insert(graphcount, QString(tr("Duplicate Ack")));
258     }
259 }
260 
drawTSNGraph(const sctp_assoc_info_t * selected_assoc)261 void SCTPGraphDialog::drawTSNGraph(const sctp_assoc_info_t* selected_assoc)
262 {
263     GList *listTSN = Q_NULLPTR,*tlist = Q_NULLPTR;
264     tsn_t *tsn = Q_NULLPTR;
265     guint8 type;
266     guint32 tsnumber=0, rel = 0, minTSN;
267 
268     if (direction == 1) {
269         listTSN = g_list_last(selected_assoc->tsn1);
270          minTSN = selected_assoc->min_tsn1;
271     } else {
272         listTSN = g_list_last(selected_assoc->tsn2);
273          minTSN = selected_assoc->min_tsn2;
274     }
275 
276     if (relative) {
277         rel = minTSN;
278      }
279 
280     while (listTSN) {
281         tsn = gxx_list_data(tsn_t*, listTSN);
282         tlist = g_list_first(tsn->tsns);
283         while (tlist)
284         {
285             type = gxx_list_data(struct chunk_header *, tlist)->type;
286             if (type == SCTP_DATA_CHUNK_ID || type == SCTP_I_DATA_CHUNK_ID || type == SCTP_FORWARD_TSN_CHUNK_ID) {
287                 tsnumber = g_ntohl(gxx_list_data(struct data_chunk_header *, tlist)->tsn);
288                 yt.append(tsnumber - rel);
289                 xt.append(tsn->secs + tsn->usecs/1000000.0);
290                 ft.append(tsn->frame_number);
291             }
292             tlist = gxx_list_next(tlist);
293         }
294         listTSN = gxx_list_previous(listTSN);
295     }
296 
297     QCPScatterStyle myScatter;
298     myScatter.setShape(QCPScatterStyle::ssCircle);
299     myScatter.setSize(3);
300 
301     int graphcount = ui->sctpPlot->graphCount();
302     // create graph and assign data to it:
303 
304     // Add TSN graph
305     if (xt.size() > 0) {
306         QCPGraph *gr = ui->sctpPlot->addGraph();
307         gr->setName(QString("TSN"));
308         myScatter.setPen(QPen(Qt::black));
309         myScatter.setBrush(Qt::black);
310         ui->sctpPlot->graph(graphcount)->setScatterStyle(myScatter);
311         ui->sctpPlot->graph(graphcount)->setLineStyle(QCPGraph::lsNone);
312         ui->sctpPlot->graph(graphcount)->setData(xt, yt);
313         typeStrings.insert(graphcount, QString(tr("TSN")));
314     }
315 }
316 
drawGraph(const sctp_assoc_info_t * selected_assoc)317 void SCTPGraphDialog::drawGraph(const sctp_assoc_info_t* selected_assoc)
318 {
319     if (!selected_assoc) {
320         selected_assoc = SCTPAssocAnalyseDialog::findAssoc(this, selected_assoc_id);
321         if (!selected_assoc) return;
322     }
323 
324     guint32 maxTSN, minTSN;
325 
326     if (direction == 1) {
327         maxTSN = selected_assoc->max_tsn1;
328         minTSN = selected_assoc->min_tsn1;
329     } else {
330         maxTSN = selected_assoc->max_tsn2;
331         minTSN = selected_assoc->min_tsn2;
332     }
333     ui->sctpPlot->clearGraphs();
334     xt.clear();
335     yt.clear();
336     xs.clear();
337     ys.clear();
338     xg.clear();
339     yg.clear();
340     xd.clear();
341     yd.clear();
342     xn.clear();
343     yn.clear();
344     ft.clear();
345     fs.clear();
346     fg.clear();
347     fd.clear();
348     fn.clear();
349     typeStrings.clear();
350     switch (type) {
351     case 1:
352         drawSACKGraph(selected_assoc);
353         drawNRSACKGraph(selected_assoc);
354         break;
355     case 2:
356         drawTSNGraph(selected_assoc);
357         break;
358     case 3:
359         drawTSNGraph(selected_assoc);
360         drawSACKGraph(selected_assoc);
361         drawNRSACKGraph(selected_assoc);
362         break;
363     default:
364         drawTSNGraph(selected_assoc);
365         drawSACKGraph(selected_assoc);
366         drawNRSACKGraph(selected_assoc);
367         break;
368     }
369 
370     // give the axes some labels:
371     ui->sctpPlot->xAxis->setLabel(tr("time [secs]"));
372     ui->sctpPlot->yAxis->setLabel(tr("TSNs"));
373     ui->sctpPlot->setInteractions(QCP::iRangeZoom | QCP::iRangeDrag | QCP::iSelectPlottables);
374     connect(ui->sctpPlot, SIGNAL(plottableClick(QCPAbstractPlottable*,QMouseEvent*)), this, SLOT(graphClicked(QCPAbstractPlottable*, QMouseEvent*)));
375     // set axes ranges, so we see all data:
376     QCPRange myXRange(selected_assoc->min_secs, (selected_assoc->max_secs+1));
377     if (relative) {
378         QCPRange myYRange(0, maxTSN - minTSN + 1);
379         ui->sctpPlot->yAxis->setRange(myYRange);
380     } else {
381         QCPRange myYRange(minTSN, maxTSN + 1);
382         ui->sctpPlot->yAxis->setRange(myYRange);
383     }
384     ui->sctpPlot->xAxis->setRange(myXRange);
385     ui->sctpPlot->replot();
386 }
387 
on_pushButton_clicked()388 void SCTPGraphDialog::on_pushButton_clicked()
389 {
390     type = 1;
391     drawGraph();
392 }
393 
on_pushButton_2_clicked()394 void SCTPGraphDialog::on_pushButton_2_clicked()
395 {
396     type = 2;
397     drawGraph();
398 }
399 
on_pushButton_3_clicked()400 void SCTPGraphDialog::on_pushButton_3_clicked()
401 {
402     type = 3;
403     drawGraph();
404 }
405 
on_pushButton_4_clicked()406 void SCTPGraphDialog::on_pushButton_4_clicked()
407 {
408     const sctp_assoc_info_t* selected_assoc = SCTPAssocAnalyseDialog::findAssoc(this, selected_assoc_id);
409     if (!selected_assoc) return;
410 
411     ui->sctpPlot->xAxis->setRange(selected_assoc->min_secs, selected_assoc->max_secs+1);
412     if (relative) {
413         if (direction == 1) {
414             ui->sctpPlot->yAxis->setRange(0, selected_assoc->max_tsn1 - selected_assoc->min_tsn1);
415         } else {
416             ui->sctpPlot->yAxis->setRange(0, selected_assoc->max_tsn2 - selected_assoc->min_tsn2);
417         }
418    } else {
419         if (direction == 1) {
420             ui->sctpPlot->yAxis->setRange(selected_assoc->min_tsn1, selected_assoc->max_tsn1);
421         } else {
422             ui->sctpPlot->yAxis->setRange(selected_assoc->min_tsn2, selected_assoc->max_tsn2);
423         }
424     }
425     ui->sctpPlot->replot();
426 }
427 
graphClicked(QCPAbstractPlottable * plottable,QMouseEvent * event)428 void SCTPGraphDialog::graphClicked(QCPAbstractPlottable* plottable, QMouseEvent* event)
429 {
430     frame_num = 0;
431     int i=0;
432     double times = ui->sctpPlot->xAxis->pixelToCoord(event->pos().x());
433     if (plottable->name().contains("TSN", Qt::CaseInsensitive)) {
434         for (i = 0; i < xt.size(); i++) {
435             if (times <= xt.value(i)) {
436                 frame_num = ft.at(i);
437                 break;
438             }
439         }
440     } else if (plottable->name().contains("SACK", Qt::CaseInsensitive)) {
441         for (i = 0; i < xs.size(); i++) {
442             if (times <= xs.value(i)) {
443                 frame_num = fs.at(i);
444                 break;
445             }
446         }
447     } else if (plottable->name().contains("DUP", Qt::CaseInsensitive)) {
448         for (i = 0; i < xd.size(); i++) {
449             if (times <= xd.value(i)) {
450                 frame_num = fd.at(i);
451                 break;
452             }
453         }
454     } else if (plottable->name().contains("NR_GAP", Qt::CaseInsensitive)) {
455         for (i = 0; i < xn.size(); i++) {
456             if (times <= xn.value(i)) {
457                 frame_num = fn.at(i);
458                 break;
459             }
460         }
461     } else if (plottable->name().contains("GAP", Qt::CaseInsensitive)) {
462         for (i = 0; i < xs.size(); i++) {
463             if (times <= xs.value(i)) {
464                 frame_num = fs.at(i);
465                 break;
466             }
467         }
468     }
469     if (cap_file_ && frame_num > 0) {
470         cf_goto_frame(cap_file_, frame_num);
471     }
472     ui->hintLabel->setText(QString(tr("<small><i>%1: %2 Time: %3 secs </i></small>"))
473                            .arg(plottable->name())
474                            .arg(floor(ui->sctpPlot->yAxis->pixelToCoord(event->pos().y()) + 0.5))
475                            .arg(ui->sctpPlot->xAxis->pixelToCoord(event->pos().x())));
476 }
477 
save_graph(QDialog * dlg,QCustomPlot * plot)478 void SCTPGraphDialog::save_graph(QDialog *dlg, QCustomPlot *plot)
479 {
480     QString file_name, extension;
481     QDir path(wsApp->lastOpenDir());
482     QString pdf_filter = tr("Portable Document Format (*.pdf)");
483     QString png_filter = tr("Portable Network Graphics (*.png)");
484     QString bmp_filter = tr("Windows Bitmap (*.bmp)");
485     // Gaze upon my beautiful graph with lossy artifacts!
486     QString jpeg_filter = tr("JPEG File Interchange Format (*.jpeg *.jpg)");
487     QString filter = QString("%1;;%2;;%3;;%4")
488             .arg(pdf_filter)
489             .arg(png_filter)
490             .arg(bmp_filter)
491             .arg(jpeg_filter);
492 
493     file_name = WiresharkFileDialog::getSaveFileName(dlg, wsApp->windowTitleString(tr("Save Graph As…")),
494                                              path.canonicalPath(), filter, &extension);
495 
496     if (file_name.length() > 0) {
497         bool save_ok = false;
498         if (extension.compare(pdf_filter) == 0) {
499             save_ok = plot->savePdf(file_name);
500         } else if (extension.compare(png_filter) == 0) {
501             save_ok = plot->savePng(file_name);
502         } else if (extension.compare(bmp_filter) == 0) {
503             save_ok = plot->saveBmp(file_name);
504         } else if (extension.compare(jpeg_filter) == 0) {
505             save_ok = plot->saveJpg(file_name);
506         }
507         // else error dialog?
508         if (save_ok) {
509             wsApp->setLastOpenDirFromFilename(file_name);
510         }
511     }
512 }
513 
514 
on_saveButton_clicked()515 void SCTPGraphDialog::on_saveButton_clicked()
516 {
517     save_graph(this, ui->sctpPlot);
518 }
519 
on_relativeTsn_stateChanged(int arg1)520 void SCTPGraphDialog::on_relativeTsn_stateChanged(int arg1)
521 {
522     relative = arg1;
523     drawGraph();
524 }
525