1 /* multicast_statistics_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 "multicast_statistics_dialog.h"
11 
12 #include <QFormLayout>
13 #include <QLabel>
14 #include <QPushButton>
15 #include <QTreeWidget>
16 
17 #include <ui/qt/utils/qt_ui_utils.h>
18 #include <ui/qt/widgets/syntax_line_edit.h>
19 #include "wireshark_application.h"
20 
21 enum {
22     col_src_addr_,
23     col_src_port_,
24     col_dst_addr_,
25     col_dst_port_,
26     col_packets_,
27     col_packets_s_,
28     col_avg_bw_,
29     col_max_bw_,
30     col_max_burst_,
31     col_burst_alarms_,
32     col_max_buffers_,
33     col_buffer_alarms_
34 };
35 
36 enum {
37     mcast_table_type_ = 1000
38 };
39 
40 class MulticastStatTreeWidgetItem : public QTreeWidgetItem
41 {
42 public:
43     MulticastStatTreeWidgetItem(QTreeWidget *parent) :
44         QTreeWidgetItem (parent, mcast_table_type_)
45     {
46         clear_address(&src_addr_);
47         clear_address(&dst_addr_);
48         src_port_ = 0;
49         dst_port_ = 0;
50         num_packets_ = 0;
51         avg_pps_ = 0;
52         avg_bw_ = 0;
53         max_bw_ = 0;
54         top_burst_size_ = 0;
55         num_bursts_ = 0;
56         top_buff_usage_ = 0;
57         num_buff_alarms_ = 0;
58     }
59 
60     void updateStreamInfo(const mcast_stream_info_t *stream_info) {
61         copy_address(&src_addr_, &stream_info->src_addr);
62         src_port_ = stream_info->src_port;
63         copy_address(&dst_addr_, &stream_info->dest_addr);
64         dst_port_ = stream_info->dest_port;
65         num_packets_ = stream_info->npackets;
66         avg_pps_ = stream_info->apackets;
67         avg_bw_ = stream_info->average_bw;
68         max_bw_ = stream_info->element.maxbw;
69         top_burst_size_ = stream_info->element.topburstsize;
70         num_bursts_ = stream_info->element.numbursts;
71         top_buff_usage_ = stream_info->element.topbuffusage;
72         num_buff_alarms_ = stream_info->element.numbuffalarms;
73 
74         draw();
75     }
76 
77     void draw() {
78         setText(col_src_addr_, address_to_qstring(&src_addr_));
79         setText(col_src_port_, QString::number(src_port_));
80         setText(col_dst_addr_, address_to_qstring(&dst_addr_));
81         setText(col_dst_port_, QString::number(dst_port_));
82         setText(col_packets_, QString::number(num_packets_));
83         setText(col_packets_s_, QString::number(avg_pps_, 'f', 2));
84         setText(col_avg_bw_, bits_s_to_qstring(avg_bw_));
85         setText(col_max_bw_, bits_s_to_qstring(max_bw_));
86         setText(col_max_burst_, QString("%1 / %2ms").arg(top_burst_size_).arg(mcast_stream_burstint));
87         setText(col_burst_alarms_, QString::number(num_bursts_));
88         setText(col_max_buffers_, bits_s_to_qstring(top_buff_usage_));
89         setText(col_buffer_alarms_, QString::number(num_buff_alarms_));
90     }
91 
92     bool operator< (const QTreeWidgetItem &other) const
93     {
94         if (other.type() != mcast_table_type_) return QTreeWidgetItem::operator< (other);
95         const MulticastStatTreeWidgetItem *other_row = static_cast<const MulticastStatTreeWidgetItem *>(&other);
96 
97         switch (treeWidget()->sortColumn()) {
98         case col_src_addr_:
99             return cmp_address(&src_addr_, &other_row->src_addr_) < 0;
100         case col_src_port_:
101             return src_port_ < other_row->src_port_;
102         case col_dst_addr_:
103             return cmp_address(&dst_addr_, &other_row->dst_addr_) < 0;
104         case col_dst_port_:
105             return dst_port_ < other_row->dst_port_;
106         case col_packets_:
107             return num_packets_ < other_row->num_packets_;
108         case col_packets_s_:
109             return avg_pps_ < other_row->avg_pps_;
110         case col_avg_bw_:
111             return avg_bw_ < other_row->avg_bw_;
112         case col_max_bw_:
113             return max_bw_ < other_row->max_bw_;
114         case col_max_burst_:
115             return top_burst_size_ < other_row->top_burst_size_;
116         case col_burst_alarms_:
117             return num_bursts_ < other_row->num_bursts_;
118         case col_max_buffers_:
119             return top_buff_usage_ < other_row->top_buff_usage_;
120         case col_buffer_alarms_:
121             return num_buff_alarms_ < other_row->num_buff_alarms_;
122         default:
123             break;
124         }
125 
126         return QTreeWidgetItem::operator< (other);
127     }
128     QList<QVariant> rowData() {
129         return QList<QVariant>()
130                 << address_to_qstring(&src_addr_) << src_port_
131                 << address_to_qstring(&dst_addr_) << dst_port_
132                 << num_packets_ << avg_pps_
133                 << avg_bw_ << max_bw_
134                 << top_burst_size_ << num_bursts_
135                 << top_buff_usage_ << num_buff_alarms_;
136     }
137     const QString filterExpression() {
138         QString ip_version;
139 
140         if (src_addr_.type == AT_IPv6) ip_version = "v6";
141 
142         const QString filter_expr = QString("(ip%1.src==%2 && udp.srcport==%3 && ip%1.dst==%4 && udp.dstport==%5)")
143                 .arg(ip_version)
144                 .arg(address_to_qstring(&src_addr_))
145                 .arg(src_port_)
146                 .arg(address_to_qstring(&dst_addr_))
147                 .arg(dst_port_);
148         return filter_expr;
149     }
150 
151 private:
152     address src_addr_;
153     guint16 src_port_;
154     address dst_addr_;
155     guint16 dst_port_;
156     unsigned num_packets_;
157     double avg_pps_;
158     double avg_bw_;
159     double max_bw_;
160     int top_burst_size_;
161     int num_bursts_;
162     int top_buff_usage_;
163     int num_buff_alarms_;
164 };
165 
166 MulticastStatisticsDialog::MulticastStatisticsDialog(QWidget &parent, CaptureFile &cf, const char *filter) :
167     TapParameterDialog(parent, cf)
168 {
169     setWindowSubtitle(tr("UDP Multicast Streams"));
170     loadGeometry(parent.width() * 4 / 5, parent.height() * 3 / 4, "MulticastStatisticsDialog");
171 
172     tapinfo_ = new mcaststream_tapinfo_t();
173     tapinfo_->user_data = this;
174     tapinfo_->tap_reset = tapReset;
175     tapinfo_->tap_draw = tapDraw;
176 
177     QStringList header_names = QStringList()
178             << tr("Source Address") << tr("Source Port")
179             << tr("Destination Address") << tr("Destination Port")
180             << tr("Packets") << tr("Packets/s")
181             << tr("Avg BW (bps)") << tr("Max BW (bps)")
182             << tr("Max Burst") << tr("Burst Alarms")
183             << tr("Max Buffers (B)") << tr("Buffer Alarms");
184 
185     statsTreeWidget()->setHeaderLabels(header_names);
186 
187     for (int col = 0; col < statsTreeWidget()->columnCount(); col++) {
188         if (col == col_src_addr_ || col == col_dst_addr_) continue;
189         statsTreeWidget()->headerItem()->setTextAlignment(col, Qt::AlignRight);
190     }
191 
192     burst_measurement_interval_le_ = new SyntaxLineEdit(this);
193     burst_alarm_threshold_le_ = new SyntaxLineEdit(this);
194     buffer_alarm_threshold_le_ = new SyntaxLineEdit(this);
195     stream_empty_speed_le_ = new SyntaxLineEdit(this);
196     total_empty_speed_le_ = new SyntaxLineEdit(this);
197 
198     int filter_layout_idx = verticalLayout()->indexOf(filterLayout()->widget());
199     QGridLayout *param_grid = new QGridLayout();
200     int one_em = fontMetrics().height();
201     verticalLayout()->insertLayout(filter_layout_idx, param_grid);
202 
203     // Label | LineEdit | | Label | LineEdit | | Label | LineEdit
204     // 0       1         2  3       4         5  6       7
205     param_grid->setColumnMinimumWidth(2, one_em * 2);
206     param_grid->setColumnStretch(2, 1);
207     param_grid->setColumnMinimumWidth(5, one_em * 2);
208     param_grid->setColumnStretch(5, 1);
209     param_grid->addWidget(new QLabel(tr("Burst measurement interval (ms):")), 0, 0, Qt::AlignRight);
210     param_grid->addWidget(burst_measurement_interval_le_, 0, 1);
211     param_grid->addWidget(new QLabel(tr("Burst alarm threshold (packets):")), 0, 3, Qt::AlignRight);
212     param_grid->addWidget(burst_alarm_threshold_le_, 0, 4);
213     param_grid->addWidget(new QLabel(tr("Buffer alarm threshold (B):")), 0, 6, Qt::AlignRight);
214     param_grid->addWidget(buffer_alarm_threshold_le_, 0, 7);
215 
216     param_grid->addWidget(new QLabel(tr("Stream empty speed (Kb/s):")), 1, 0, Qt::AlignRight);
217     param_grid->addWidget(stream_empty_speed_le_, 1, 1);
218     param_grid->addWidget(new QLabel(tr("Total empty speed (Kb/s):")), 1, 3, Qt::AlignRight);
219     param_grid->addWidget(total_empty_speed_le_, 1, 4);
220 
221     burst_measurement_interval_le_->setText(QString::number(mcast_stream_burstint));
222     burst_alarm_threshold_le_->setText(QString::number(mcast_stream_trigger));
223     buffer_alarm_threshold_le_->setText(QString::number(mcast_stream_bufferalarm));
224     stream_empty_speed_le_->setText(QString::number(mcast_stream_emptyspeed));
225     total_empty_speed_le_->setText(QString::number(mcast_stream_cumulemptyspeed));
226 
227     line_edits_ = QList<QWidget *>()
228             << burst_measurement_interval_le_ << burst_alarm_threshold_le_
229             << buffer_alarm_threshold_le_ << stream_empty_speed_le_
230             << total_empty_speed_le_;
231 
232     foreach (QWidget *line_edit, line_edits_) {
233         line_edit->setMinimumWidth(one_em * 5);
234         connect(line_edit, SIGNAL(textEdited(QString)), this, SLOT(updateWidgets()));
235     }
236 
237     addFilterActions();
238 
239     if (filter) {
240         setDisplayFilter(filter);
241     }
242 
243     connect(this, SIGNAL(updateFilter(QString)),
244             this, SLOT(updateMulticastParameters()));
245 
246     /* Register the tap listener */
247     register_tap_listener_mcast_stream(tapinfo_);
248 
249     updateWidgets();
250 }
251 
252 MulticastStatisticsDialog::~MulticastStatisticsDialog()
253 {
254     /* Remove the stream tap listener */
255     remove_tap_listener_mcast_stream(tapinfo_);
256 
257     /* Clean up memory used by stream tap */
258     mcaststream_reset(tapinfo_);
259 
260     delete tapinfo_;
261 }
262 
263 void MulticastStatisticsDialog::tapReset(mcaststream_tapinfo_t *tapinfo)
264 {
265     MulticastStatisticsDialog *ms_dlg = dynamic_cast<MulticastStatisticsDialog *>((MulticastStatisticsDialog*)tapinfo->user_data);
266     if (!ms_dlg || !ms_dlg->statsTreeWidget()) return;
267 
268     ms_dlg->statsTreeWidget()->clear();
269 }
270 
271 void MulticastStatisticsDialog::tapDraw(mcaststream_tapinfo_t *tapinfo)
272 {
273     MulticastStatisticsDialog *ms_dlg = dynamic_cast<MulticastStatisticsDialog *>((MulticastStatisticsDialog*)tapinfo->user_data);
274     if (!ms_dlg || !ms_dlg->statsTreeWidget()) return;
275 
276     //Clear the tree because the list always starts from the beginning
277     ms_dlg->statsTreeWidget()->clear();
278 
279     // Add missing rows and update stats
280     int cur_row = 0;
281     for (GList *cur = g_list_first(tapinfo->strinfo_list); cur; cur = gxx_list_next(cur)) {
282         mcast_stream_info_t *stream_info = gxx_list_data(mcast_stream_info_t *, cur);
283         if (!stream_info) continue;
284 
285         MulticastStatTreeWidgetItem *ms_ti;
286         QTreeWidgetItem *ti = ms_dlg->statsTreeWidget()->topLevelItem(cur_row);
287         if (!ti) {
288             ms_ti = new MulticastStatTreeWidgetItem(ms_dlg->statsTreeWidget());
289             for (int col = 0; col < ms_dlg->statsTreeWidget()->columnCount(); col++) {
290                 if (col == col_src_addr_ || col == col_dst_addr_) continue;
291                 ms_ti->setTextAlignment(col, Qt::AlignRight);
292             }
293         } else {
294             ms_ti = static_cast<MulticastStatTreeWidgetItem *>(ti);
295         }
296 
297         ms_ti->updateStreamInfo(stream_info);
298         cur_row++;
299     }
300 }
301 
302 QList<QVariant> MulticastStatisticsDialog::treeItemData(QTreeWidgetItem *ti) const
303 {
304     MulticastStatTreeWidgetItem *ms_ti = dynamic_cast<MulticastStatTreeWidgetItem*>(ti);
305     if (ms_ti) {
306         return ms_ti->rowData();
307     }
308     else {
309         return QList<QVariant>();
310     }
311 }
312 
313 const QString MulticastStatisticsDialog::filterExpression()
314 {
315     QString filter_expr;
316     if (statsTreeWidget()->selectedItems().count() > 0) {
317         QTreeWidgetItem *ti = statsTreeWidget()->selectedItems()[0];
318 
319         MulticastStatTreeWidgetItem *ms_ti = static_cast<MulticastStatTreeWidgetItem *>(ti);
320         filter_expr = ms_ti->filterExpression();
321     }
322     return filter_expr;
323 }
324 
325 void MulticastStatisticsDialog::updateWidgets()
326 {
327     QString hint;
328     bool enable_apply = true;
329     bool enable_edits = cap_file_.isValid();
330     bool ok = false;
331     int param;
332 
333     param = burst_measurement_interval_le_->text().toUInt(&ok);
334     if (!ok || param < 1 || param > 1000) {
335         hint += tr("The burst interval must be between 1 and 1000. ");
336         enable_apply = false;
337         burst_measurement_interval_le_->setSyntaxState(SyntaxLineEdit::Invalid);
338     } else {
339         burst_measurement_interval_le_->setSyntaxState(SyntaxLineEdit::Valid);
340     }
341 
342     param = burst_alarm_threshold_le_->text().toInt(&ok);
343     if (!ok || param < 1) {
344         hint += tr("The burst alarm threshold isn't valid. ");
345         enable_apply = false;
346         burst_alarm_threshold_le_->setSyntaxState(SyntaxLineEdit::Invalid);
347     } else {
348         burst_alarm_threshold_le_->setSyntaxState(SyntaxLineEdit::Valid);
349     }
350 
351     param = buffer_alarm_threshold_le_->text().toInt(&ok);
352     if (!ok || param < 1) {
353         hint += tr("The buffer alarm threshold isn't valid. ");
354         enable_apply = false;
355         buffer_alarm_threshold_le_->setSyntaxState(SyntaxLineEdit::Invalid);
356     } else {
357         buffer_alarm_threshold_le_->setSyntaxState(SyntaxLineEdit::Valid);
358     }
359 
360     param = stream_empty_speed_le_->text().toInt(&ok);
361     if (!ok || param < 1 || param > 10000000) {
362         hint += tr("The stream empty speed should be between 1 and 10000000. ");
363         enable_apply = false;
364         stream_empty_speed_le_->setSyntaxState(SyntaxLineEdit::Invalid);
365     } else {
366         stream_empty_speed_le_->setSyntaxState(SyntaxLineEdit::Valid);
367     }
368 
369     param = total_empty_speed_le_->text().toInt(&ok);
370     if (!ok || param < 1 || param > 10000000) {
371         hint += tr("The total empty speed should be between 1 and 10000000. ");
372         enable_apply = false;
373         total_empty_speed_le_->setSyntaxState(SyntaxLineEdit::Invalid);
374     } else {
375         total_empty_speed_le_->setSyntaxState(SyntaxLineEdit::Valid);
376     }
377 
378     foreach (QWidget *line_edit, line_edits_) {
379         line_edit->setEnabled(enable_edits);
380     }
381 
382     applyFilterButton()->setEnabled(enable_apply);
383 
384     if (hint.isEmpty() && tapinfo_->allstreams) {
385         const QString stats = tr("%1 streams, avg bw: %2bps, max bw: %3bps, max burst: %4 / %5ms, max buffer: %6B")
386                 .arg(statsTreeWidget()->topLevelItemCount())
387                 .arg(bits_s_to_qstring(tapinfo_->allstreams->average_bw))
388                 .arg(bits_s_to_qstring(tapinfo_->allstreams->element.maxbw))
389                 .arg(tapinfo_->allstreams->element.topburstsize)
390                 .arg(mcast_stream_burstint)
391                 .arg(bits_s_to_qstring(tapinfo_->allstreams->element.topbuffusage));
392         hint.append(stats);
393     }
394     hint.prepend("<small><i>");
395     hint.append("</i></small>");
396     setHint(hint);
397     TapParameterDialog::updateWidgets();
398 }
399 
400 void MulticastStatisticsDialog::updateMulticastParameters()
401 {
402     bool ok = false;
403     int param;
404 
405     param = burst_measurement_interval_le_->text().toUInt(&ok);
406     if (ok && param > 0 && param <= 1000) {
407         mcast_stream_burstint = (guint16) param;
408     }
409 
410     param = burst_alarm_threshold_le_->text().toInt(&ok);
411     if (ok) {
412         mcast_stream_trigger = param;
413     }
414 
415     param = buffer_alarm_threshold_le_->text().toInt(&ok);
416     if (ok && param > 0) {
417         mcast_stream_bufferalarm = param;
418     }
419 
420     param = stream_empty_speed_le_->text().toInt(&ok);
421     if (ok && param > 0 && param <= 10000000) {
422         mcast_stream_emptyspeed = param;
423     }
424 
425     param = total_empty_speed_le_->text().toInt(&ok);
426     if (ok && param > 0 && param <= 10000000) {
427         mcast_stream_cumulemptyspeed = param;
428     }
429 }
430 
431 void MulticastStatisticsDialog::fillTree()
432 {
433     QList<QWidget *> disable_widgets = QList<QWidget *>()
434             << line_edits_ << displayFilterLineEdit() << applyFilterButton();
435 
436     foreach (QWidget *w, disable_widgets) w->setEnabled(false);
437 
438     /* Scan for Mcast streams (redissect all packets) */
439     mcaststream_scan(tapinfo_, cap_file_.capFile());
440     tapDraw(tapinfo_);
441 
442     foreach (QWidget *w, disable_widgets) w->setEnabled(true);
443     for (int col = 0; col < statsTreeWidget()->columnCount() - 1; col++) {
444         statsTreeWidget()->resizeColumnToContents(col);
445     }
446     updateWidgets();
447 }
448 
449 void MulticastStatisticsDialog::captureFileClosing()
450 {
451     /* Remove the stream tap listener */
452     remove_tap_listener_mcast_stream(tapinfo_);
453 
454     WiresharkDialog::captureFileClosing();
455 }
456 
457 // Stat command + args
458 
459 static void
460 multicast_statistics_init(const char *args, void*) {
461     QStringList args_l = QString(args).split(',');
462     QByteArray filter;
463     if (args_l.length() > 2) {
464         filter = QStringList(args_l.mid(2)).join(",").toUtf8();
465     }
466     wsApp->emitStatCommandSignal("MulticastStatistics", filter.constData(), NULL);
467 }
468 
469 static stat_tap_ui multicast_statistics_ui = {
470     REGISTER_STAT_GROUP_GENERIC,
471     NULL,
472     "multicast,stat",
473     multicast_statistics_init,
474     0,
475     NULL
476 };
477 
478 extern "C" {
479 
480 void register_tap_listener_qt_multicast_statistics(void);
481 
482 void
483 register_tap_listener_qt_multicast_statistics(void)
484 {
485     register_stat_tap_ui(&multicast_statistics_ui, NULL);
486 }
487 
488 }
489