diff options
author | Gerald Combs <gerald@wireshark.org> | 2014-08-06 10:07:42 -0700 |
---|---|---|
committer | Gerald Combs <gerald@wireshark.org> | 2014-09-05 01:06:53 +0000 |
commit | 30f3d524411445c26418c757ac9deb0940afc409 (patch) | |
tree | d60cadae8fdfb0612ee6e00bc5f17b550534af29 /ui/qt/endpoint_dialog.cpp | |
parent | 382815d6bdd9a66421ae2e07c734df4c8a4ac398 (diff) | |
download | wireshark-30f3d524411445c26418c757ac9deb0940afc409.tar.gz |
Qt: Refactor ConversationDialog for endpoints.
Create a TrafficTableDialog (for lack of a better name) parent class
from the general parts of ConversationDialog. Use it to create
EndpointsDialog.
Move the contents of conversation_tree_widget.{cpp,h} to
conversation_dialog.{cpp,h} to match endpoint_dialog and
traffic_table_dialog.
Fill in GeoIP columns dynamically instead of using a hard-coded limit.
Use "endp_" and "ENDP_" prefixes for a lot of endpoint variables and
defines.
Try to make geoip_db_lookup_ipv4 and geoip_db_lookup_ipv6 more robust.
Clean up some includes. Fix a shadowed variable.
Change-Id: I23054816ac7f8c6edb3b1f01c8536db37ba4122d
Reviewed-on: https://code.wireshark.org/review/3462
Petri-Dish: Gerald Combs <gerald@wireshark.org>
Tested-by: Petri Dish Buildbot <buildbot-no-reply@wireshark.org>
Reviewed-by: Gerald Combs <gerald@wireshark.org>
Diffstat (limited to 'ui/qt/endpoint_dialog.cpp')
-rw-r--r-- | ui/qt/endpoint_dialog.cpp | 545 |
1 files changed, 545 insertions, 0 deletions
diff --git a/ui/qt/endpoint_dialog.cpp b/ui/qt/endpoint_dialog.cpp new file mode 100644 index 0000000000..1523819d6d --- /dev/null +++ b/ui/qt/endpoint_dialog.cpp @@ -0,0 +1,545 @@ +/* endpoint_dialog.cpp + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "endpoint_dialog.h" + +#ifdef HAVE_GEOIP +#include <GeoIP.h> +#include <epan/geoip_db.h> +#include <wsutil/pint.h> +#endif + +#include "ui/recent.h" +#include "ui/traffic_table_ui.h" + +#include "wsutil/str_util.h" + +#include "qt_ui_utils.h" + +#include "wireshark_application.h" + +#include <QMessageBox> + + +const QString table_name_ = QObject::tr("Endpoint"); +EndpointDialog::EndpointDialog(QWidget *parent, capture_file *cf, int cli_proto_id, const char *filter) : + TrafficTableDialog(parent, cf, filter, table_name_) +{ + QList<int> endp_protos; + for (GList *endp_tab = recent.endpoint_tabs; endp_tab; endp_tab = endp_tab->next) { + int proto_id = proto_get_id_by_short_name((const char *)endp_tab->data); + if (proto_id > -1 && !endp_protos.contains(proto_id)) { + endp_protos.append(proto_id); + } + } + + if (endp_protos.isEmpty()) { + endp_protos = defaultProtos(); + } + + // Bring the command-line specified type to the front. + if (get_conversation_by_proto_id(cli_proto_id)) { + endp_protos.removeAll(cli_proto_id); + endp_protos.prepend(cli_proto_id); + } + + // QTabWidget selects the first item by default. + foreach (int endp_proto, endp_protos) { + addTrafficTable(get_conversation_by_proto_id(endp_proto)); + } + + fillTypeMenu(endp_protos); + + updateWidgets(); + itemSelectionChanged(); + + if (cap_file_) { + cf_retap_packets(cap_file_); + } +} + +EndpointDialog::~EndpointDialog() +{ + prefs_clear_string_list(recent.endpoint_tabs); + recent.endpoint_tabs = NULL; + + EndpointTreeWidget *cur_tree = qobject_cast<EndpointTreeWidget *>(trafficTableTabWidget()->currentWidget()); + foreach (QAction *ea, traffic_type_menu_.actions()) { + int proto_id = ea->data().value<int>(); + if (proto_id_to_tree_.contains(proto_id) && ea->isChecked()) { + char *title = g_strdup(proto_get_protocol_short_name(find_protocol_by_id(proto_id))); + if (proto_id_to_tree_[proto_id] == cur_tree) { + recent.endpoint_tabs = g_list_prepend(recent.endpoint_tabs, title); + } else { + recent.endpoint_tabs = g_list_append(recent.endpoint_tabs, title); + } + } + } +} + +void EndpointDialog::setCaptureFile(capture_file *cf) +{ + if (!cf) { // We only want to know when the file closes. + cap_file_ = NULL; + for (int i = 0; i < trafficTableTabWidget()->count(); i++) { + EndpointTreeWidget *cur_tree = qobject_cast<EndpointTreeWidget *>(trafficTableTabWidget()->widget(i)); + remove_tap_listener(cur_tree->trafficTreeHash()); + disconnect(cur_tree, SIGNAL(filterAction(QString&,FilterAction::Action,FilterAction::ActionType)), + this, SIGNAL(filterAction(QString&,FilterAction::Action,FilterAction::ActionType))); + } + displayFilterCheckBox()->setEnabled(false); + enabledTypesPushButton()->setEnabled(false); + } +} + +bool EndpointDialog::addTrafficTable(register_ct_t *table) +{ + int proto_id = get_conversation_proto_id(table); + + if (!table || proto_id_to_tree_.contains(proto_id)) { + return false; + } + + EndpointTreeWidget *endp_tree = new EndpointTreeWidget(this, table); + + proto_id_to_tree_[proto_id] = endp_tree; + const char* table_name = proto_get_protocol_short_name(find_protocol_by_id(proto_id)); + + trafficTableTabWidget()->addTab(endp_tree, table_name); + + connect(endp_tree, SIGNAL(itemSelectionChanged()), + this, SLOT(itemSelectionChanged())); + connect(endp_tree, SIGNAL(titleChanged(QWidget*,QString)), + this, SLOT(setTabText(QWidget*,QString))); + connect(endp_tree, SIGNAL(filterAction(QString&,FilterAction::Action,FilterAction::ActionType)), + this, SIGNAL(filterAction(QString&,FilterAction::Action,FilterAction::ActionType))); + connect(nameResolutionCheckBox(), SIGNAL(toggled(bool)), + endp_tree, SLOT(setNameResolutionEnabled(bool))); + + // XXX Move to ConversationTreeWidget ctor? + const char *filter = NULL; + if (displayFilterCheckBox()->isChecked()) { + filter = cap_file_->dfilter; + } else if (!filter_.isEmpty()) { + filter = filter_.toUtf8().constData(); + } + + endp_tree->trafficTreeHash()->user_data = endp_tree; + + GString *error_string = register_tap_listener(proto_get_protocol_filter_name(proto_id), endp_tree->trafficTreeHash(), filter, 0, + EndpointTreeWidget::tapReset, + get_hostlist_packet_func(table), + EndpointTreeWidget::tapDraw); + + if (error_string) { + QMessageBox::warning(this, tr("Endpoint %1 failed to register tap listener").arg(table_name), + error_string->str); + g_string_free(error_string, TRUE); + } + + return true; +} + +void EndpointDialog::on_buttonBox_helpRequested() +{ + wsApp->helpTopicAction(HELP_STATS_ENDPOINTS_DIALOG); +} + +void init_endpoint_table(struct register_ct* ct, const char *filter) +{ + Q_UNUSED(ct) + wsApp->emitStatCommandSignal("Endpoints", filter, GINT_TO_POINTER(get_conversation_proto_id(ct))); +} + +// EndpointTreeWidgetItem +// TrafficTableTreeWidgetItem / QTreeWidgetItem subclass that allows sorting + +const int ei_col_ = 0; +const int pkts_col_ = 1; + +const char *geoip_none_ = "-"; + +class EndpointTreeWidgetItem : public TrafficTableTreeWidgetItem +{ +public: + EndpointTreeWidgetItem(QTreeWidget *tree) : TrafficTableTreeWidgetItem(tree) {} + EndpointTreeWidgetItem(QTreeWidget *parent, const QStringList &strings) + : TrafficTableTreeWidgetItem (parent, strings) {} + + // Set column text to its cooked representation. + void update(gboolean resolve_names) { + hostlist_talker_t *endp_item = data(ei_col_, Qt::UserRole).value<hostlist_talker_t *>(); + bool ok; + quint64 cur_packets = data(pkts_col_, Qt::UserRole).toULongLong(&ok); + + if (!endp_item) { + return; + } + + quint64 packets = endp_item->tx_frames + endp_item->rx_frames; + if (ok && cur_packets == packets) { + return; + } + + setText(ENDP_COLUMN_ADDR, get_conversation_address(&endp_item->myaddress, resolve_names)); + setText(ENDP_COLUMN_PORT, get_conversation_port(endp_item->port, endp_item->ptype, resolve_names)); + + QString col_str; + + col_str = QString("%L1").arg(packets); + setText(ENDP_COLUMN_PACKETS, col_str); + col_str = gchar_free_to_qstring(format_size(endp_item->tx_bytes + endp_item->rx_bytes, format_size_unit_none|format_size_prefix_si)); + setText(ENDP_COLUMN_BYTES, col_str); + col_str = QString("%L1").arg(endp_item->tx_frames); + setText(ENDP_COLUMN_PKT_AB, QString::number(endp_item->tx_frames)); + col_str = gchar_free_to_qstring(format_size(endp_item->tx_bytes, format_size_unit_none|format_size_prefix_si)); + setText(ENDP_COLUMN_BYTES_AB, col_str); + col_str = QString("%L1").arg(endp_item->rx_frames); + setText(ENDP_COLUMN_PKT_BA, QString::number(endp_item->rx_frames)); + col_str = gchar_free_to_qstring(format_size(endp_item->rx_bytes, format_size_unit_none|format_size_prefix_si)); + setText(ENDP_COLUMN_BYTES_BA, col_str); + setData(pkts_col_, Qt::UserRole, qVariantFromValue(packets)); + +#ifdef HAVE_GEOIP + /* Filled in from the GeoIP config, if any */ + for (unsigned i = 0; i < geoip_db_num_dbs(); i++) { + if (endp_item->myaddress.type == AT_IPv4) { + setText(ENDP_NUM_COLUMNS+i, geoip_db_lookup_ipv4(i, pntoh32(endp_item->myaddress.data), geoip_none_)); + } else if (endp_item->myaddress.type == AT_IPv6) { + const struct e_in6_addr *addr = (const struct e_in6_addr *) endp_item->myaddress.data; + setText(ENDP_NUM_COLUMNS+i, geoip_db_lookup_ipv6(i, *addr, geoip_none_)); + } else { + setText(ENDP_NUM_COLUMNS+i, geoip_none_); + } + } +#endif + } + + // Return a string, qulonglong, double, or invalid QVariant representing the raw column data. + QVariant colData(int col, bool resolve_names) const { + hostlist_talker_t *endp_item = data(ei_col_, Qt::UserRole).value<hostlist_talker_t *>(); + + if (!endp_item) { + return QVariant(); + } + + switch (col) { + case ENDP_COLUMN_ADDR: + return get_conversation_address(&endp_item->myaddress, resolve_names); + case ENDP_COLUMN_PORT: + if (resolve_names) { + return get_conversation_port(endp_item->port, endp_item->ptype, resolve_names); + } else { + return quint32(endp_item->port); + } + case ENDP_COLUMN_PACKETS: + return quint64(endp_item->tx_frames + endp_item->rx_frames); + case ENDP_COLUMN_BYTES: + return quint64(endp_item->tx_bytes + endp_item->rx_bytes); + case ENDP_COLUMN_PKT_AB: + return quint64(endp_item->tx_frames); + case ENDP_COLUMN_BYTES_AB: + return quint64(endp_item->tx_bytes); + case ENDP_COLUMN_PKT_BA: + return quint64(endp_item->rx_frames); + case ENDP_COLUMN_BYTES_BA: + return quint64(endp_item->rx_bytes); +#ifdef HAVE_GEOIP + default: + { + bool ok; + + double dval = text(col).toDouble(&ok); + if (ok) { // Assume lat / lon + return dval; + } + + qulonglong ullval = text(col).toULongLong(&ok); + if (ok) { // Assume lat / lon + return ullval; + } + + qlonglong llval = text(col).toLongLong(&ok); + if (ok) { // Assume lat / lon + return llval; + } + + return text(col); + break; + } +#else + default: + return QVariant(); +#endif + } + } + + bool operator< (const QTreeWidgetItem &other) const + { + hostlist_talker_t *endp_item = data(ei_col_, Qt::UserRole).value<hostlist_talker_t *>(); + hostlist_talker_t *other_item = other.data(ei_col_, Qt::UserRole).value<hostlist_talker_t *>(); + + if (!endp_item || !other_item) { + return false; + } + + int sort_col = treeWidget()->sortColumn(); + + switch(sort_col) { + case ENDP_COLUMN_ADDR: + return cmp_address(&endp_item->myaddress, &other_item->myaddress) < 0 ? true : false; + case ENDP_COLUMN_PORT: + return endp_item->port < other_item->port; + case ENDP_COLUMN_PACKETS: + return (endp_item->tx_frames + endp_item->rx_frames) < (other_item->tx_frames + other_item->rx_frames); + case ENDP_COLUMN_BYTES: + return (endp_item->tx_bytes + endp_item->rx_bytes) < (other_item->tx_bytes + other_item->rx_bytes); + case ENDP_COLUMN_PKT_AB: + return endp_item->tx_frames < other_item->tx_frames; + case ENDP_COLUMN_BYTES_AB: + return endp_item->tx_bytes < other_item->tx_bytes; + case ENDP_COLUMN_PKT_BA: + return endp_item->rx_frames < other_item->rx_frames; + case ENDP_COLUMN_BYTES_BA: + return endp_item->rx_bytes < other_item->rx_bytes; +#ifdef HAVE_GEOIP + default: + { + double ei_val, oi_val; + bool ei_ok, oi_ok; + ei_val = text(sort_col).toDouble(&ei_ok); + oi_val = other.text(sort_col).toDouble(&oi_ok); + + if (ei_ok && oi_ok) { // Assume lat / lon + return ei_val < oi_val; + } else { + // XXX Fall back to string comparison. We might want to try sorting naturally + // using QCollator instead. + return text(sort_col) < other.text(sort_col); + } + break; + } +#else + default: + return false; +#endif + } + } + +}; + +// +// EndpointTreeWidget +// TrafficTableTreeWidget / QTreeWidget subclass that allows tapping +// + +EndpointTreeWidget::EndpointTreeWidget(QWidget *parent, register_ct_t *table) : + TrafficTableTreeWidget(parent, table) +{ +#ifdef HAVE_GEOIP + setColumnCount(ENDP_NUM_COLUMNS + geoip_db_num_dbs()); +#else + setColumnCount(ENDP_NUM_BUILTIN_COLUMNS); +#endif + + for (int i = 0; i < ENDP_NUM_COLUMNS; i++) { + headerItem()->setText(i, endp_column_titles[i]); + } + + if (get_conversation_hide_ports(table_)) { + hideColumn(ENDP_COLUMN_PORT); + } else if (!strcmp(proto_get_protocol_filter_name(get_conversation_proto_id(table_)), "ncp")) { + headerItem()->setText(ENDP_COLUMN_PORT, endp_conn_title); + } + +#ifdef HAVE_GEOIP + for (unsigned i = 0; i < geoip_db_num_dbs(); i++) { + headerItem()->setText(ENDP_NUM_COLUMNS + i, geoip_db_name(i)); + hideColumn(ENDP_NUM_COLUMNS + i); + } +#endif + + int one_en = fontMetrics().height() / 2; + for (int i = 0; i < columnCount(); i++) { + switch (i) { + case ENDP_COLUMN_ADDR: + setColumnWidth(i, one_en * strlen("000.000.000.000")); + break; + case ENDP_COLUMN_PORT: + setColumnWidth(i, one_en * strlen("000000")); + break; + case ENDP_COLUMN_PACKETS: + case ENDP_COLUMN_PKT_AB: + case ENDP_COLUMN_PKT_BA: + setColumnWidth(i, one_en * strlen("00,000")); + break; + case ENDP_COLUMN_BYTES: + case ENDP_COLUMN_BYTES_AB: + case ENDP_COLUMN_BYTES_BA: + setColumnWidth(i, one_en * strlen("000,000")); + break; + default: + setColumnWidth(i, one_en * strlen("-00.000000")); // GeoIP + } + } + + QMenu *submenu; + + FilterAction::Action cur_action = FilterAction::ActionApply; + submenu = ctx_menu_.addMenu(FilterAction::actionName(cur_action)); + foreach (FilterAction::ActionType at, FilterAction::actionTypes()) { + FilterAction *fa = new FilterAction(submenu, cur_action, at); + submenu->addAction(fa); + connect(fa, SIGNAL(triggered()), this, SLOT(filterActionTriggered())); + } + + cur_action = FilterAction::ActionPrepare; + submenu = ctx_menu_.addMenu(FilterAction::actionName(cur_action)); + foreach (FilterAction::ActionType at, FilterAction::actionTypes()) { + FilterAction *fa = new FilterAction(submenu, cur_action, at); + submenu->addAction(fa); + connect(fa, SIGNAL(triggered()), this, SLOT(filterActionTriggered())); + } + + cur_action = FilterAction::ActionFind; + submenu = ctx_menu_.addMenu(FilterAction::actionName(cur_action)); + foreach (FilterAction::ActionType at, FilterAction::actionTypes()) { + FilterAction *fa = new FilterAction(submenu, cur_action, at); + submenu->addAction(fa); + connect(fa, SIGNAL(triggered()), this, SLOT(filterActionTriggered())); + } + + cur_action = FilterAction::ActionColorize; + submenu = ctx_menu_.addMenu(FilterAction::actionName(cur_action)); + foreach (FilterAction::ActionType at, FilterAction::actionTypes()) { + FilterAction *fa = new FilterAction(submenu, cur_action, at); + submenu->addAction(fa); + connect(fa, SIGNAL(triggered()), this, SLOT(filterActionTriggered())); + } + + updateItems(); + +} + +EndpointTreeWidget::~EndpointTreeWidget() +{ + reset_hostlist_table_data(&hash_); +} + +void EndpointTreeWidget::tapReset(void *conv_hash_ptr) +{ + conv_hash_t *hash = (conv_hash_t*)conv_hash_ptr; + EndpointTreeWidget *endp_tree = static_cast<EndpointTreeWidget *>(hash->user_data); + if (!endp_tree) return; + + endp_tree->clear(); + reset_hostlist_table_data(&endp_tree->hash_); +} + +void EndpointTreeWidget::tapDraw(void *conv_hash_ptr) +{ + conv_hash_t *hash = (conv_hash_t*)conv_hash_ptr; + EndpointTreeWidget *endp_tree = static_cast<EndpointTreeWidget *>(hash->user_data); + if (!endp_tree) return; + + endp_tree->updateItems(); +} + +void EndpointTreeWidget::updateItems() +{ + title_ = proto_get_protocol_short_name(find_protocol_by_id(get_conversation_proto_id(table_))); + + if (hash_.conv_array && hash_.conv_array->len > 0) { + title_.append(QString(" %1 %2").arg(UTF8_MIDDLE_DOT).arg(hash_.conv_array->len)); + } + emit titleChanged(this, title_); + + if (!hash_.conv_array) { + return; + } + +#ifdef HAVE_GEOIP + if (topLevelItemCount() < 1 && hash_.conv_array->len > 0) { + hostlist_talker_t *endp_item = &g_array_index(hash_.conv_array, hostlist_talker_t, 0); + if (endp_item->myaddress.type == AT_IPv4 || endp_item->myaddress.type == AT_IPv6) { + for (unsigned i = 0; i < geoip_db_num_dbs(); i++) { + showColumn(ENDP_NUM_COLUMNS + i); + } + } + } +#endif + + setSortingEnabled(false); + for (int i = topLevelItemCount(); i < (int) hash_.conv_array->len; i++) { + EndpointTreeWidgetItem *etwi = new EndpointTreeWidgetItem(this); + hostlist_talker_t *endp_item = &g_array_index(hash_.conv_array, hostlist_talker_t, i); + etwi->setData(ei_col_, Qt::UserRole, qVariantFromValue(endp_item)); + addTopLevelItem(etwi); + + for (int col = 0; col < columnCount(); col++) { + if (col != ENDP_COLUMN_ADDR && col < ENDP_NUM_COLUMNS) { + etwi->setTextAlignment(col, Qt::AlignRight); + } + } + } + QTreeWidgetItemIterator iter(this); + while (*iter) { + EndpointTreeWidgetItem *ei = static_cast<EndpointTreeWidgetItem *>(*iter); + ei->update(resolve_names_); + ++iter; + } + setSortingEnabled(true); + + for (int col = 0; col < columnCount(); col++) { + resizeColumnToContents(col); + } +} + +void EndpointTreeWidget::filterActionTriggered() +{ + EndpointTreeWidgetItem *etwi = static_cast<EndpointTreeWidgetItem *>(currentItem()); + FilterAction *fa = qobject_cast<FilterAction *>(QObject::sender()); + + if (!fa || !etwi) { + return; + } + + hostlist_talker_t *endp_item = etwi->data(ei_col_, Qt::UserRole).value<hostlist_talker_t *>(); + if (!endp_item) { + return; + } + + QString filter = get_hostlist_filter(endp_item); + emit filterAction(filter, fa->action(), fa->actionType()); +} + +/* + * Editor modelines + * + * Local Variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * ex: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ |