3 * Wireshark - Network traffic analyzer
4 * By Gerald Combs <gerald@wireshark.org>
5 * Copyright 1998 Gerald Combs
7 * SPDX-License-Identifier: GPL-2.0+*/
9 #include "endpoint_dialog.h"
13 #include <epan/geoip_db.h>
14 #include <wsutil/pint.h>
17 #include <epan/prefs.h>
19 #include "ui/recent.h"
20 #include "ui/traffic_table_ui.h"
22 #include "wsutil/str_util.h"
24 #include <ui/qt/utils/qt_ui_utils.h>
25 #include "wireshark_application.h"
28 #include <QDesktopServices>
29 #include <QDialogButtonBox>
30 #include <QMessageBox>
31 #include <QPushButton>
34 static const QString table_name_ = QObject::tr("Endpoint");
35 EndpointDialog::EndpointDialog(QWidget &parent, CaptureFile &cf, int cli_proto_id, const char *filter) :
36 TrafficTableDialog(parent, cf, filter, table_name_)
39 map_bt_ = buttonBox()->addButton(tr("Map"), QDialogButtonBox::ActionRole);
40 map_bt_->setToolTip(tr("Draw IPv4 or IPv6 endpoints on a map."));
41 connect(map_bt_, SIGNAL(clicked()), this, SLOT(createMap()));
43 connect(trafficTableTabWidget(), SIGNAL(currentChanged(int)), this, SLOT(tabChanged()));
46 addProgressFrame(&parent);
48 QList<int> endp_protos;
49 for (GList *endp_tab = recent.endpoint_tabs; endp_tab; endp_tab = endp_tab->next) {
50 int proto_id = proto_get_id_by_short_name((const char *)endp_tab->data);
51 if (proto_id > -1 && !endp_protos.contains(proto_id)) {
52 endp_protos.append(proto_id);
56 if (endp_protos.isEmpty()) {
57 endp_protos = defaultProtos();
60 // Bring the command-line specified type to the front.
61 if ((cli_proto_id > 0) && (get_conversation_by_proto_id(cli_proto_id))) {
62 endp_protos.removeAll(cli_proto_id);
63 endp_protos.prepend(cli_proto_id);
66 // QTabWidget selects the first item by default.
67 foreach (int endp_proto, endp_protos) {
68 addTrafficTable(get_conversation_by_proto_id(endp_proto));
71 fillTypeMenu(endp_protos);
77 QPushButton *close_bt = buttonBox()->button(QDialogButtonBox::Close);
79 close_bt->setDefault(true);
83 // currentTabChanged();
85 cap_file_.delayedRetapPackets();
88 EndpointDialog::~EndpointDialog()
90 prefs_clear_string_list(recent.endpoint_tabs);
91 recent.endpoint_tabs = NULL;
93 EndpointTreeWidget *cur_tree = qobject_cast<EndpointTreeWidget *>(trafficTableTabWidget()->currentWidget());
94 foreach (QAction *ea, traffic_type_menu_.actions()) {
95 int proto_id = ea->data().value<int>();
96 if (proto_id_to_tree_.contains(proto_id) && ea->isChecked()) {
97 char *title = g_strdup(proto_get_protocol_short_name(find_protocol_by_id(proto_id)));
98 if (proto_id_to_tree_[proto_id] == cur_tree) {
99 recent.endpoint_tabs = g_list_prepend(recent.endpoint_tabs, title);
101 recent.endpoint_tabs = g_list_append(recent.endpoint_tabs, title);
107 void EndpointDialog::captureFileClosing()
109 // Keep the dialog around but disable any controls that depend
110 // on a live capture file.
111 for (int i = 0; i < trafficTableTabWidget()->count(); i++) {
112 EndpointTreeWidget *cur_tree = qobject_cast<EndpointTreeWidget *>(trafficTableTabWidget()->widget(i));
113 disconnect(cur_tree, SIGNAL(filterAction(QString,FilterAction::Action,FilterAction::ActionType)),
114 this, SIGNAL(filterAction(QString,FilterAction::Action,FilterAction::ActionType)));
116 displayFilterCheckBox()->setEnabled(false);
117 enabledTypesPushButton()->setEnabled(false);
118 TrafficTableDialog::captureFileClosing();
121 bool EndpointDialog::addTrafficTable(register_ct_t *table)
123 int proto_id = get_conversation_proto_id(table);
125 if (!table || proto_id_to_tree_.contains(proto_id)) {
129 EndpointTreeWidget *endp_tree = new EndpointTreeWidget(this, table);
131 proto_id_to_tree_[proto_id] = endp_tree;
132 const char* table_name = proto_get_protocol_short_name(find_protocol_by_id(proto_id));
134 trafficTableTabWidget()->addTab(endp_tree, table_name);
136 connect(endp_tree, SIGNAL(titleChanged(QWidget*,QString)),
137 this, SLOT(setTabText(QWidget*,QString)));
138 connect(endp_tree, SIGNAL(filterAction(QString,FilterAction::Action,FilterAction::ActionType)),
139 this, SIGNAL(filterAction(QString,FilterAction::Action,FilterAction::ActionType)));
140 connect(nameResolutionCheckBox(), SIGNAL(toggled(bool)),
141 endp_tree, SLOT(setNameResolutionEnabled(bool)));
143 // XXX Move to ConversationTreeWidget ctor?
144 QByteArray filter_utf8;
145 const char *filter = NULL;
146 if (displayFilterCheckBox()->isChecked()) {
147 filter = cap_file_.capFile()->dfilter;
148 } else if (!filter_.isEmpty()) {
149 filter_utf8 = filter_.toUtf8();
150 filter = filter_utf8.constData();
153 endp_tree->trafficTreeHash()->user_data = endp_tree;
155 registerTapListener(proto_get_protocol_filter_name(proto_id), endp_tree->trafficTreeHash(), filter, 0,
156 EndpointTreeWidget::tapReset,
157 get_hostlist_packet_func(table),
158 EndpointTreeWidget::tapDraw);
161 connect(endp_tree, SIGNAL(geoIPStatusChanged()), this, SLOT(tabChanged()));
167 void EndpointDialog::tabChanged()
169 EndpointTreeWidget *cur_tree = qobject_cast<EndpointTreeWidget *>(trafficTableTabWidget()->currentWidget());
170 map_bt_->setEnabled(cur_tree && cur_tree->hasGeoIPData());
173 void EndpointDialog::createMap()
175 EndpointTreeWidget *cur_tree = qobject_cast<EndpointTreeWidget *>(trafficTableTabWidget()->currentWidget());
181 gchar *map_path = create_endpoint_geoip_map(cur_tree->trafficTreeHash()->conv_array, &err_str);
183 QMessageBox::warning(this, tr("Map file error"), err_str);
187 QDesktopServices::openUrl(QUrl::fromLocalFile(gchar_free_to_qstring(map_path)));
191 void EndpointDialog::on_buttonBox_helpRequested()
193 wsApp->helpTopicAction(HELP_STATS_ENDPOINTS_DIALOG);
196 void init_endpoint_table(struct register_ct* ct, const char *filter)
198 wsApp->emitStatCommandSignal("Endpoints", filter, GINT_TO_POINTER(get_conversation_proto_id(ct)));
201 // EndpointTreeWidgetItem
202 // TrafficTableTreeWidgetItem / QTreeWidgetItem subclass that allows sorting
205 static const char *geoip_none_ = UTF8_EM_DASH;
208 class EndpointTreeWidgetItem : public TrafficTableTreeWidgetItem
211 EndpointTreeWidgetItem(GArray *conv_array, guint conv_idx, bool *resolve_names_ptr) :
212 TrafficTableTreeWidgetItem(NULL),
213 conv_array_(conv_array),
215 resolve_names_ptr_(resolve_names_ptr)
218 hostlist_talker_t *hostlistTalker() {
219 return &g_array_index(conv_array_, hostlist_talker_t, conv_idx_);
222 virtual QVariant data(int column, int role) const {
223 if (role == Qt::DisplayRole) {
224 // Column text cooked representation.
225 hostlist_talker_t *endp_item = &g_array_index(conv_array_, hostlist_talker_t, conv_idx_);
227 bool resolve_names = false;
228 if (resolve_names_ptr_ && *resolve_names_ptr_) resolve_names = true;
230 case ENDP_COLUMN_PACKETS:
231 return QString("%L1").arg(endp_item->tx_frames + endp_item->rx_frames);
232 case ENDP_COLUMN_BYTES:
233 return gchar_free_to_qstring(format_size(endp_item->tx_bytes + endp_item->rx_bytes, format_size_unit_none|format_size_prefix_si));
234 case ENDP_COLUMN_PKT_AB:
235 return QString("%L1").arg(endp_item->tx_frames);
236 case ENDP_COLUMN_BYTES_AB:
237 return gchar_free_to_qstring(format_size(endp_item->tx_bytes, format_size_unit_none|format_size_prefix_si));
238 case ENDP_COLUMN_PKT_BA:
239 return QString("%L1").arg(endp_item->rx_frames);
240 case ENDP_COLUMN_BYTES_BA:
241 return gchar_free_to_qstring(format_size(endp_item->rx_bytes, format_size_unit_none|format_size_prefix_si));
245 QString geoip_str = colData(column, resolve_names, true).toString();
246 if (geoip_str.isEmpty()) geoip_str = geoip_none_;
251 return colData(column, resolve_names, true);
255 return QTreeWidgetItem::data(column, role);
258 // Column text raw representation.
259 // Return a string, qulonglong, double, or invalid QVariant representing the raw column data.
260 QVariant colData(int col, bool resolve_names, bool strings_only) const {
262 Q_UNUSED(strings_only)
264 hostlist_talker_t *endp_item = &g_array_index(conv_array_, hostlist_talker_t, conv_idx_);
267 case ENDP_COLUMN_ADDR:
269 char* addr_str = get_conversation_address(NULL, &endp_item->myaddress, resolve_names);
270 QString q_addr_str(addr_str);
271 wmem_free(NULL, addr_str);
274 case ENDP_COLUMN_PORT:
276 char* port_str = get_conversation_port(NULL, endp_item->port, endp_item->etype, resolve_names);
277 QString q_port_str(port_str);
278 wmem_free(NULL, port_str);
281 return quint32(endp_item->port);
283 case ENDP_COLUMN_PACKETS:
284 return quint64(endp_item->tx_frames + endp_item->rx_frames);
285 case ENDP_COLUMN_BYTES:
286 return quint64(endp_item->tx_bytes + endp_item->rx_bytes);
287 case ENDP_COLUMN_PKT_AB:
288 return quint64(endp_item->tx_frames);
289 case ENDP_COLUMN_BYTES_AB:
290 return quint64(endp_item->tx_bytes);
291 case ENDP_COLUMN_PKT_BA:
292 return quint64(endp_item->rx_frames);
293 case ENDP_COLUMN_BYTES_BA:
294 return quint64(endp_item->rx_bytes);
299 /* Filled in from the GeoIP config, if any */
300 EndpointTreeWidget *ep_tree = qobject_cast<EndpointTreeWidget *>(treeWidget());
301 if (!ep_tree) return geoip_str;
302 foreach (unsigned db, ep_tree->columnToDb(col)) {
303 if (endp_item->myaddress.type == AT_IPv4) {
304 geoip_str = geoip_db_lookup_ipv4(db, pntoh32(endp_item->myaddress.data), NULL);
305 } else if (endp_item->myaddress.type == AT_IPv6) {
306 const ws_in6_addr *addr = (const ws_in6_addr *) endp_item->myaddress.data;
307 geoip_str = geoip_db_lookup_ipv6(db, *addr, NULL);
309 if (!geoip_str.isEmpty()) {
314 if (strings_only) return geoip_str;
318 double dval = geoip_str.toDouble(&ok);
319 if (ok) { // Assume lat / lon
323 qulonglong ullval = geoip_str.toULongLong(&ok);
324 if (ok) { // Assume uint
328 qlonglong llval = geoip_str.toLongLong(&ok);
329 if (ok) { // Assume int
342 virtual QVariant colData(int col, bool resolve_names) const { return colData(col, resolve_names, false); }
344 bool operator< (const QTreeWidgetItem &other) const
346 const EndpointTreeWidgetItem *other_row = static_cast<const EndpointTreeWidgetItem *>(&other);
347 hostlist_talker_t *endp_item = &g_array_index(conv_array_, hostlist_talker_t, conv_idx_);
348 hostlist_talker_t *other_item = &g_array_index(other_row->conv_array_, hostlist_talker_t, other_row->conv_idx_);
350 int sort_col = treeWidget()->sortColumn();
353 case ENDP_COLUMN_ADDR:
354 return cmp_address(&endp_item->myaddress, &other_item->myaddress) < 0 ? true : false;
355 case ENDP_COLUMN_PORT:
356 return endp_item->port < other_item->port;
357 case ENDP_COLUMN_PACKETS:
358 return (endp_item->tx_frames + endp_item->rx_frames) < (other_item->tx_frames + other_item->rx_frames);
359 case ENDP_COLUMN_BYTES:
360 return (endp_item->tx_bytes + endp_item->rx_bytes) < (other_item->tx_bytes + other_item->rx_bytes);
361 case ENDP_COLUMN_PKT_AB:
362 return endp_item->tx_frames < other_item->tx_frames;
363 case ENDP_COLUMN_BYTES_AB:
364 return endp_item->tx_bytes < other_item->tx_bytes;
365 case ENDP_COLUMN_PKT_BA:
366 return endp_item->rx_frames < other_item->rx_frames;
367 case ENDP_COLUMN_BYTES_BA:
368 return endp_item->rx_bytes < other_item->rx_bytes;
372 double ei_val, oi_val;
374 ei_val = text(sort_col).toDouble(&ei_ok);
375 oi_val = other.text(sort_col).toDouble(&oi_ok);
377 if (ei_ok && oi_ok) { // Assume lat / lon
378 return ei_val < oi_val;
380 // XXX Fall back to string comparison. We might want to try sorting naturally
381 // using QCollator instead.
382 return text(sort_col) < other.text(sort_col);
394 bool *resolve_names_ptr_;
398 // EndpointTreeWidget
399 // TrafficTableTreeWidget / QTreeWidget subclass that allows tapping
402 EndpointTreeWidget::EndpointTreeWidget(QWidget *parent, register_ct_t *table) :
403 TrafficTableTreeWidget(parent, table)
405 , has_geoip_data_(false)
408 setColumnCount(ENDP_NUM_COLUMNS);
409 setUniformRowHeights(true);
411 for (int i = 0; i < ENDP_NUM_COLUMNS; i++) {
412 headerItem()->setText(i, endp_column_titles[i]);
415 if (get_conversation_hide_ports(table_)) {
416 hideColumn(ENDP_COLUMN_PORT);
417 } else if (!strcmp(proto_get_protocol_filter_name(get_conversation_proto_id(table_)), "ncp")) {
418 headerItem()->setText(ENDP_COLUMN_PORT, endp_conn_title);
422 QMap<QString, int> db_name_to_col;
423 for (unsigned db = 0; db < geoip_db_num_dbs(); db++) {
424 QString db_name = geoip_db_name(db);
425 int col = db_name_to_col.value(db_name, -1);
429 setColumnCount(col + 1);
430 headerItem()->setText(col, db_name);
432 db_name_to_col[db_name] = col;
434 col_to_db_[col] << db;
438 int one_en = fontMetrics().height() / 2;
439 for (int i = 0; i < columnCount(); i++) {
441 case ENDP_COLUMN_ADDR:
442 setColumnWidth(i, one_en * (int) strlen("000.000.000.000"));
444 case ENDP_COLUMN_PORT:
445 setColumnWidth(i, one_en * (int) strlen("000000"));
447 case ENDP_COLUMN_PACKETS:
448 case ENDP_COLUMN_PKT_AB:
449 case ENDP_COLUMN_PKT_BA:
450 setColumnWidth(i, one_en * (int) strlen("00,000"));
452 case ENDP_COLUMN_BYTES:
453 case ENDP_COLUMN_BYTES_AB:
454 case ENDP_COLUMN_BYTES_BA:
455 setColumnWidth(i, one_en * (int) strlen("000,000"));
458 setColumnWidth(i, one_en * (int) strlen("-00.000000")); // GeoIP
464 FilterAction::Action cur_action = FilterAction::ActionApply;
465 submenu = ctx_menu_.addMenu(FilterAction::actionName(cur_action));
466 foreach (FilterAction::ActionType at, FilterAction::actionTypes()) {
467 FilterAction *fa = new FilterAction(submenu, cur_action, at);
468 submenu->addAction(fa);
469 connect(fa, SIGNAL(triggered()), this, SLOT(filterActionTriggered()));
472 cur_action = FilterAction::ActionPrepare;
473 submenu = ctx_menu_.addMenu(FilterAction::actionName(cur_action));
474 foreach (FilterAction::ActionType at, FilterAction::actionTypes()) {
475 FilterAction *fa = new FilterAction(submenu, cur_action, at);
476 submenu->addAction(fa);
477 connect(fa, SIGNAL(triggered()), this, SLOT(filterActionTriggered()));
480 cur_action = FilterAction::ActionFind;
481 submenu = ctx_menu_.addMenu(FilterAction::actionName(cur_action));
482 foreach (FilterAction::ActionType at, FilterAction::actionTypes()) {
483 FilterAction *fa = new FilterAction(submenu, cur_action, at);
484 submenu->addAction(fa);
485 connect(fa, SIGNAL(triggered()), this, SLOT(filterActionTriggered()));
488 cur_action = FilterAction::ActionColorize;
489 submenu = ctx_menu_.addMenu(FilterAction::actionName(cur_action));
490 foreach (FilterAction::ActionType at, FilterAction::actionTypes()) {
491 FilterAction *fa = new FilterAction(submenu, cur_action, at);
492 submenu->addAction(fa);
493 connect(fa, SIGNAL(triggered()), this, SLOT(filterActionTriggered()));
500 EndpointTreeWidget::~EndpointTreeWidget()
502 reset_hostlist_table_data(&hash_);
505 void EndpointTreeWidget::tapReset(void *conv_hash_ptr)
507 conv_hash_t *hash = (conv_hash_t*)conv_hash_ptr;
508 EndpointTreeWidget *endp_tree = qobject_cast<EndpointTreeWidget *>((EndpointTreeWidget *)hash->user_data);
509 if (!endp_tree) return;
512 reset_hostlist_table_data(&endp_tree->hash_);
515 void EndpointTreeWidget::tapDraw(void *conv_hash_ptr)
517 conv_hash_t *hash = (conv_hash_t*)conv_hash_ptr;
518 EndpointTreeWidget *endp_tree = qobject_cast<EndpointTreeWidget *>((EndpointTreeWidget *)hash->user_data);
519 if (!endp_tree) return;
521 endp_tree->updateItems();
524 void EndpointTreeWidget::updateItems()
526 bool resize = topLevelItemCount() < resizeThreshold();
527 title_ = proto_get_protocol_short_name(find_protocol_by_id(get_conversation_proto_id(table_)));
529 if (hash_.conv_array && hash_.conv_array->len > 0) {
530 title_.append(QString(" %1 %2").arg(UTF8_MIDDLE_DOT).arg(hash_.conv_array->len));
532 emit titleChanged(this, title_);
534 if (!hash_.conv_array) {
539 if (topLevelItemCount() < 1 && hash_.conv_array->len > 0) {
540 hostlist_talker_t *endp_item = &g_array_index(hash_.conv_array, hostlist_talker_t, 0);
541 if (endp_item->myaddress.type == AT_IPv4 || endp_item->myaddress.type == AT_IPv6) {
542 for (unsigned i = 0; i < geoip_db_num_dbs(); i++) {
543 showColumn(ENDP_NUM_COLUMNS + i);
545 has_geoip_data_ = true;
546 emit geoIPStatusChanged();
551 setSortingEnabled(false);
553 QList<QTreeWidgetItem *>new_items;
554 for (int i = topLevelItemCount(); i < (int) hash_.conv_array->len; i++) {
555 EndpointTreeWidgetItem *etwi = new EndpointTreeWidgetItem(hash_.conv_array, i, &resolve_names_);
558 for (int col = 0; col < columnCount(); col++) {
559 if (col != ENDP_COLUMN_ADDR && col < ENDP_NUM_COLUMNS) {
560 etwi->setTextAlignment(col, Qt::AlignRight);
564 addTopLevelItems(new_items);
565 setSortingEnabled(true);
568 for (int col = 0; col < columnCount(); col++) {
569 resizeColumnToContents(col);
574 void EndpointTreeWidget::filterActionTriggered()
576 EndpointTreeWidgetItem *etwi = static_cast<EndpointTreeWidgetItem *>(currentItem());
577 FilterAction *fa = qobject_cast<FilterAction *>(QObject::sender());
583 hostlist_talker_t *endp_item = etwi->hostlistTalker();
588 QString filter = get_hostlist_filter(endp_item);
589 emit filterAction(filter, fa->action(), fa->actionType());
598 * indent-tabs-mode: nil
601 * ex: set shiftwidth=4 tabstop=8 expandtab:
602 * :indentSize=4:tabSize=8:noTabs=true: