1 /* traffic_table_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 "traffic_table_dialog.h"
11 #include <ui_traffic_table_dialog.h>
12 
13 #include <epan/addr_resolv.h>
14 #include <epan/prefs.h>
15 
16 #include "ui/recent.h"
17 
18 #include "progress_frame.h"
19 #include "wireshark_application.h"
20 
21 #include <QCheckBox>
22 #include <QClipboard>
23 #include <QContextMenuEvent>
24 #include <QDialogButtonBox>
25 #include <QList>
26 #include <QMap>
27 #include <QMessageBox>
28 #include <QPushButton>
29 #include <QTabWidget>
30 #include <QTreeWidget>
31 #include <QTextStream>
32 #include <QToolButton>
33 
34 // To do:
35 // - Add "copy" items to the menu.
36 
37 // Bugs:
38 // - Tabs and menu items don't always end up in the same order.
39 // - Columns don't resize correctly.
40 // - Closing the capture file clears conversation data.
41 
TrafficTableDialog(QWidget & parent,CaptureFile & cf,const char * filter,const QString & table_name)42 TrafficTableDialog::TrafficTableDialog(QWidget &parent, CaptureFile &cf, const char *filter, const QString &table_name) :
43     WiresharkDialog(parent, cf),
44     ui(new Ui::TrafficTableDialog),
45     filter_(filter),
46     nanosecond_timestamps_(false)
47 {
48     ui->setupUi(this);
49     loadGeometry(parent.width(), parent.height() * 3 / 4);
50 
51     ui->enabledTypesPushButton->setText(tr("%1 Types").arg(table_name));
52     ui->absoluteTimeCheckBox->hide();
53     setWindowSubtitle(QString("%1s").arg(table_name));
54 
55     copy_bt_ = ui->buttonBox->addButton(tr("Copy"), QDialogButtonBox::ActionRole);
56     QMenu *copy_menu = new QMenu(copy_bt_);
57     QAction *ca;
58     ca = copy_menu->addAction(tr("as CSV"));
59     ca->setToolTip(tr("Copy all values of this page to the clipboard in CSV (Comma Separated Values) format."));
60     connect(ca, SIGNAL(triggered()), this, SLOT(copyAsCsv()));
61     ca = copy_menu->addAction(tr("as YAML"));
62     ca->setToolTip(tr("Copy all values of this page to the clipboard in the YAML data serialization format."));
63     connect(ca, SIGNAL(triggered()), this, SLOT(copyAsYaml()));
64     copy_bt_->setMenu(copy_menu);
65 
66     ui->enabledTypesPushButton->setMenu(&traffic_type_menu_);
67     ui->trafficTableTabWidget->setFocus();
68 
69     if (cf.timestampPrecision() == WTAP_TSPREC_NSEC) {
70         nanosecond_timestamps_ = true;
71     }
72 
73     connect(wsApp, SIGNAL(addressResolutionChanged()), this, SLOT(currentTabChanged()));
74     connect(wsApp, SIGNAL(addressResolutionChanged()), this, SLOT(updateWidgets()));
75     connect(ui->trafficTableTabWidget, SIGNAL(currentChanged(int)),
76             this, SLOT(currentTabChanged()));
77     connect(&cap_file_, SIGNAL(captureEvent(CaptureEvent)),
78             this, SLOT(captureEvent(CaptureEvent)));
79 }
80 
~TrafficTableDialog()81 TrafficTableDialog::~TrafficTableDialog()
82 {
83     delete ui;
84 }
85 
absoluteStartTime()86 bool TrafficTableDialog::absoluteStartTime()
87 {
88     return absoluteTimeCheckBox()->isChecked();
89 }
90 
defaultProtos() const91 const QList<int> TrafficTableDialog::defaultProtos() const
92 {
93     // Reasonable defaults?
94     return QList<int>() << proto_get_id_by_filter_name("eth") << proto_get_id_by_filter_name("ip")
95                         << proto_get_id_by_filter_name("ipv6") << proto_get_id_by_filter_name("tcp")
96                         << proto_get_id_by_filter_name("udp");
97 }
98 
99 class fillTypeMenuData
100 {
101 public:
fillTypeMenuData(TrafficTableDialog * dialog,QList<int> & enabled_protos)102     fillTypeMenuData(TrafficTableDialog* dialog, QList<int> &enabled_protos)
103     : dialog_(dialog),
104     enabled_protos_(enabled_protos)
105     {
106     }
107 
108     TrafficTableDialog* dialog_;
109     QList<int> &enabled_protos_;
110 };
111 
fillTypeMenuFunc(const void * key,void * value,void * userdata)112 gboolean TrafficTableDialog::fillTypeMenuFunc(const void *key, void *value, void *userdata)
113 {
114     register_ct_t* ct = (register_ct_t*)value;
115     const QString title = (const gchar*)key;
116     fillTypeMenuData* data = (fillTypeMenuData*)userdata;
117     int proto_id = get_conversation_proto_id(ct);
118 
119     QAction *endp_action = new QAction(title, data->dialog_);
120     endp_action->setData(QVariant::fromValue(proto_id));
121     endp_action->setCheckable(true);
122     endp_action->setChecked(data->enabled_protos_.contains(proto_id));
123     data->dialog_->connect(endp_action, SIGNAL(triggered()), data->dialog_, SLOT(toggleTable()));
124     data->dialog_->traffic_type_menu_.addAction(endp_action);
125 
126     return FALSE;
127 }
128 
fillTypeMenu(QList<int> & enabled_protos)129 void TrafficTableDialog::fillTypeMenu(QList<int> &enabled_protos)
130 {
131     fillTypeMenuData data(this, enabled_protos);
132 
133     conversation_table_iterate_tables(fillTypeMenuFunc, &data);
134 }
135 
addProgressFrame(QObject * parent)136 void TrafficTableDialog::addProgressFrame(QObject *parent)
137 {
138     ProgressFrame::addToButtonBox(ui->buttonBox, parent);
139 }
140 
buttonBox() const141 QDialogButtonBox *TrafficTableDialog::buttonBox() const
142 {
143     return ui->buttonBox;
144 }
145 
trafficTableTabWidget() const146 QTabWidget *TrafficTableDialog::trafficTableTabWidget() const
147 {
148     return ui->trafficTableTabWidget;
149 }
150 
displayFilterCheckBox() const151 QCheckBox *TrafficTableDialog::displayFilterCheckBox() const
152 {
153     return ui->displayFilterCheckBox;
154 }
155 
nameResolutionCheckBox() const156 QCheckBox *TrafficTableDialog::nameResolutionCheckBox() const
157 {
158     return ui->nameResolutionCheckBox;
159 }
160 
absoluteTimeCheckBox() const161 QCheckBox *TrafficTableDialog::absoluteTimeCheckBox() const
162 {
163     return ui->absoluteTimeCheckBox;
164 }
165 
enabledTypesPushButton() const166 QPushButton *TrafficTableDialog::enabledTypesPushButton() const
167 {
168     return ui->enabledTypesPushButton;
169 }
170 
currentTabChanged()171 void TrafficTableDialog::currentTabChanged()
172 {
173     bool has_resolution = false;
174     TrafficTableTreeWidget *cur_tree = qobject_cast<TrafficTableTreeWidget *>(ui->trafficTableTabWidget->currentWidget());
175     if (cur_tree) has_resolution = cur_tree->hasNameResolution();
176 
177     bool block = blockSignals(true);
178     if (has_resolution) {
179         // Don't change the actual setting.
180         ui->nameResolutionCheckBox->setEnabled(true);
181     } else {
182         ui->nameResolutionCheckBox->setChecked(false);
183         ui->nameResolutionCheckBox->setEnabled(false);
184     }
185     blockSignals(block);
186 
187     if (cur_tree) cur_tree->setNameResolutionEnabled(ui->nameResolutionCheckBox->isChecked());
188 }
189 
on_nameResolutionCheckBox_toggled(bool)190 void TrafficTableDialog::on_nameResolutionCheckBox_toggled(bool)
191 {
192     QWidget *cw = ui->trafficTableTabWidget->currentWidget();
193     if (cw) cw->update();
194 }
195 
on_displayFilterCheckBox_toggled(bool checked)196 void TrafficTableDialog::on_displayFilterCheckBox_toggled(bool checked)
197 {
198     if (!cap_file_.isValid()) {
199         return;
200     }
201 
202     QByteArray filter_utf8;
203     const char *filter = NULL;
204     if (checked) {
205         filter = cap_file_.capFile()->dfilter;
206     } else if (!filter_.isEmpty()) {
207         filter_utf8 = filter_.toUtf8();
208         filter = filter_utf8.constData();
209     }
210 
211     for (int i = 0; i < ui->trafficTableTabWidget->count(); i++) {
212         TrafficTableTreeWidget *cur_tree = qobject_cast<TrafficTableTreeWidget *>(ui->trafficTableTabWidget->widget(i));
213         set_tap_dfilter(cur_tree->trafficTreeHash(), filter);
214     }
215 
216     cap_file_.retapPackets();
217 }
218 
captureEvent(CaptureEvent e)219 void TrafficTableDialog::captureEvent(CaptureEvent e)
220 {
221     if (e.captureContext() == CaptureEvent::Retap)
222     {
223         switch (e.eventType())
224         {
225         case CaptureEvent::Started:
226             ui->displayFilterCheckBox->setEnabled(false);
227             break;
228         case CaptureEvent::Finished:
229             ui->displayFilterCheckBox->setEnabled(true);
230             break;
231         default:
232             break;
233         }
234     }
235 
236 }
237 
setTabText(QWidget * tree,const QString & text)238 void TrafficTableDialog::setTabText(QWidget *tree, const QString &text)
239 {
240     // Could use QObject::sender as well
241     int index = ui->trafficTableTabWidget->indexOf(tree);
242     if (index >= 0) {
243         ui->trafficTableTabWidget->setTabText(index, text);
244     }
245 }
246 
toggleTable()247 void TrafficTableDialog::toggleTable()
248 {
249     QAction *ca = qobject_cast<QAction *>(QObject::sender());
250     if (!ca) {
251         return;
252     }
253 
254     int proto_id = ca->data().value<int>();
255     register_ct_t* table = get_conversation_by_proto_id(proto_id);
256 
257     bool new_table = addTrafficTable(table);
258     updateWidgets();
259 
260     if (ca->isChecked()) {
261         ui->trafficTableTabWidget->setCurrentWidget(proto_id_to_tree_[proto_id]);
262     }
263 
264     if (new_table) {
265         cap_file_.retapPackets();
266     }
267 }
268 
updateWidgets()269 void TrafficTableDialog::updateWidgets()
270 {
271     QWidget *cur_w = ui->trafficTableTabWidget->currentWidget();
272     ui->trafficTableTabWidget->setUpdatesEnabled(false);
273     ui->trafficTableTabWidget->clear();
274 
275     foreach (QAction *ca, traffic_type_menu_.actions()) {
276         int proto_id = ca->data().value<int>();
277         if (proto_id_to_tree_.contains(proto_id) && ca->isChecked()) {
278             ui->trafficTableTabWidget->addTab(proto_id_to_tree_[proto_id],
279                                               proto_id_to_tree_[proto_id]->trafficTreeTitle());
280         }
281     }
282     ui->trafficTableTabWidget->setCurrentWidget(cur_w);
283     ui->trafficTableTabWidget->setUpdatesEnabled(true);
284 
285     WiresharkDialog::updateWidgets();
286 }
287 
curTreeRowData(int row) const288 QList<QVariant> TrafficTableDialog::curTreeRowData(int row) const
289 {
290     TrafficTableTreeWidget *cur_tree = qobject_cast<TrafficTableTreeWidget *>(ui->trafficTableTabWidget->currentWidget());
291     if (!cur_tree) {
292         return QList<QVariant>();
293     }
294 
295     return cur_tree->rowData(row);
296 }
297 
copyAsCsv()298 void TrafficTableDialog::copyAsCsv()
299 {
300     QTreeWidget *cur_tree = qobject_cast<QTreeWidget *>(ui->trafficTableTabWidget->currentWidget());
301     if (!cur_tree) {
302         return;
303     }
304 
305     QString csv;
306     QTextStream stream(&csv, QIODevice::Text);
307     for (int row = -1; row < cur_tree->topLevelItemCount(); row ++) {
308         QStringList rdsl;
309         foreach (QVariant v, curTreeRowData(row)) {
310             if (!v.isValid()) {
311                 rdsl << "\"\"";
312             } else if (v.type() == QVariant::String) {
313                 rdsl << QString("\"%1\"").arg(v.toString());
314             } else {
315                 rdsl << v.toString();
316             }
317         }
318         stream << rdsl.join(",") << '\n';
319     }
320     wsApp->clipboard()->setText(stream.readAll());
321 }
322 
copyAsYaml()323 void TrafficTableDialog::copyAsYaml()
324 {
325     QTreeWidget *cur_tree = qobject_cast<QTreeWidget *>(ui->trafficTableTabWidget->currentWidget());
326     if (!cur_tree) {
327         return;
328     }
329 
330     QString yaml;
331     QTextStream stream(&yaml, QIODevice::Text);
332     stream << "---" << '\n';
333     for (int row = -1; row < cur_tree->topLevelItemCount(); row ++) {
334         stream << "-" << '\n';
335         foreach (QVariant v, curTreeRowData(row)) {
336             stream << " - " << v.toString() << '\n';
337         }
338     }
339     wsApp->clipboard()->setText(stream.readAll());
340 }
341 
TrafficTableTreeWidget(QWidget * parent,register_ct_t * table)342 TrafficTableTreeWidget::TrafficTableTreeWidget(QWidget *parent, register_ct_t *table) :
343     QTreeWidget(parent),
344     table_(table),
345     hash_(),
346     resolve_names_(false)
347 {
348     setRootIsDecorated(false);
349     sortByColumn(0, Qt::AscendingOrder);
350 
351     connect(wsApp, SIGNAL(addressResolutionChanged()), this, SLOT(updateItemsForSettingChange()));
352 }
353 
rowData(int row) const354 QList<QVariant> TrafficTableTreeWidget::rowData(int row) const
355 {
356     QList<QVariant> row_data;
357 
358     if (row >= topLevelItemCount()) {
359         return row_data;
360     }
361 
362     for (int col = 0; col < columnCount(); col++) {
363         if (isColumnHidden(col)) {
364             continue;
365         }
366         if (row < 0) {
367             row_data << headerItem()->text(col);
368         } else {
369             TrafficTableTreeWidgetItem *ti = static_cast<TrafficTableTreeWidgetItem *>(topLevelItem(row));
370             if (ti) {
371                 row_data << ti->colData(col, resolve_names_);
372             }
373         }
374     }
375     return row_data;
376 }
377 
378 // True if name resolution is enabled for the table's address type, false
379 // otherwise.
380 // XXX We need a more reliable method of fetching the address type(s) for
381 // a table.
hasNameResolution() const382 bool TrafficTableTreeWidget::hasNameResolution() const
383 {
384     if (!table_) return false;
385 
386     QStringList mac_protos = QStringList() << "eth" << "tr"<< "wlan";
387     QStringList net_protos = QStringList() << "ip" << "ipv6" << "jxta"
388                                            << "mptcp" << "rsvp" << "sctp"
389                                            << "tcp" << "udp";
390 
391     QString table_proto = proto_get_protocol_filter_name(get_conversation_proto_id(table_));
392 
393     if (mac_protos.contains(table_proto) && gbl_resolv_flags.mac_name) return true;
394     if (net_protos.contains(table_proto) && gbl_resolv_flags.network_name) return true;
395 
396     return false;
397 }
398 
setNameResolutionEnabled(bool enable)399 void TrafficTableTreeWidget::setNameResolutionEnabled(bool enable)
400 {
401     if (resolve_names_ != enable) {
402         resolve_names_ = enable;
403         updateItems();
404     }
405 }
406 
contextMenuEvent(QContextMenuEvent * event)407 void TrafficTableTreeWidget::contextMenuEvent(QContextMenuEvent *event)
408 {
409     bool enable = currentItem() != NULL ? true : false;
410 
411     foreach (QMenu *submenu, ctx_menu_.findChildren<QMenu*>()) {
412         submenu->setEnabled(enable);
413     }
414 
415     ctx_menu_.exec(event->globalPos());
416 
417 }
418 
updateItemsForSettingChange()419 void TrafficTableTreeWidget::updateItemsForSettingChange()
420 {
421     updateItems();
422 }
423