diff options
Diffstat (limited to 'ui/qt/voip_calls_dialog.cpp')
-rw-r--r-- | ui/qt/voip_calls_dialog.cpp | 508 |
1 files changed, 508 insertions, 0 deletions
diff --git a/ui/qt/voip_calls_dialog.cpp b/ui/qt/voip_calls_dialog.cpp new file mode 100644 index 0000000000..aee0e08121 --- /dev/null +++ b/ui/qt/voip_calls_dialog.cpp @@ -0,0 +1,508 @@ +/* voip_calls_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 "voip_calls_dialog.h" +#include "ui_voip_calls_dialog.h" + +#include "file.h" + +#include "epan/addr_resolv.h" +#include "epan/dissectors/packet-h225.h" + +#include "ui/utf8_entities.h" + +#include "sequence_dialog.h" +#include "stock_icon.h" +#include "wireshark_application.h" + +#include <QContextMenuEvent> +#include <QPushButton> + +// To do: +// - More context menu items +// - Don't select on right click +// - Player +// - Add a screenshot to the user's guide + +// Bugs: +// - Preparing a filter overwrites the existing filter. The GTK+ UI appends. +// We'll probably have to add an "append" parameter to MainWindow::filterPackets. + +// VoipCallsTreeWidgetItem +// QTreeWidgetItem subclass that allows sorting + +const int start_time_col_ = 0; +const int stop_time_col_ = 1; +const int initial_speaker_col_ = 2; +const int from_col_ = 3; +const int to_col_ = 4; +const int protocol_col_ = 5; +const int packets_col_ = 6; +const int state_col_ = 7; +const int comments_col_ = 8; + +Q_DECLARE_METATYPE(voip_calls_info_t*) + +class VoipCallsTreeWidgetItem : public QTreeWidgetItem +{ +public: + VoipCallsTreeWidgetItem(QTreeWidget *tree, voip_calls_info_t *call_info) : QTreeWidgetItem(tree) { + setData(0, Qt::UserRole, qVariantFromValue(call_info)); + drawData(); + } + + void drawData() { + voip_calls_info_t *call_info = data(0, Qt::UserRole).value<voip_calls_info_t*>(); + if (!call_info) { + return; + } + + // XXX Pull digit count from capture file precision + setText(start_time_col_, QString::number(nstime_to_sec(&(call_info->start_rel_ts)), 'f', 6)); + setText(stop_time_col_, QString::number(nstime_to_sec(&(call_info->stop_rel_ts)), 'f', 6)); + setText(initial_speaker_col_, ep_address_to_display(&(call_info->initial_speaker))); + setText(from_col_, call_info->from_identity); + setText(to_col_, call_info->to_identity); + setText(protocol_col_, ((call_info->protocol == VOIP_COMMON) && call_info->protocol_name) ? + call_info->protocol_name : voip_protocol_name[call_info->protocol]); + setText(packets_col_, QString::number(call_info->npackets)); + setText(state_col_, voip_call_state_name[call_info->call_state]); + + /* Add comments based on the protocol */ + QString call_comments; + switch (call_info->protocol) { + case VOIP_ISUP: + { + isup_calls_info_t *isup_info = (isup_calls_info_t *)call_info->prot_info; + call_comments = QString("%1-%2 %3 %4-%5") + .arg(isup_info->ni) + .arg(isup_info->opc) + .arg(UTF8_RIGHTWARDS_ARROW) + .arg(isup_info->ni) + .arg(isup_info->dpc); + } + break; + case VOIP_H323: + { + h323_calls_info_t *h323_info = (h323_calls_info_t *)call_info->prot_info; + gboolean flag = FALSE; + static const QString on_str = QObject::tr("On"); + static const QString off_str = QObject::tr("Off"); + if (call_info->call_state == VOIP_CALL_SETUP) { + flag = h323_info->is_faststart_Setup; + } else { + if ((h323_info->is_faststart_Setup) && (h323_info->is_faststart_Proc)) { + flag = TRUE; + } + } + call_comments = QObject::tr("Tunneling: %1 Fast Start: %2") + .arg(h323_info->is_h245Tunneling ? on_str : off_str) + .arg(flag ? on_str : off_str); + } + break; + case VOIP_COMMON: + default: + call_comments = call_info->call_comment; + break; + } + setText(comments_col_, call_comments); + } + + bool operator< (const QTreeWidgetItem &other) const + { + voip_calls_info_t *this_call_info = data(0, Qt::UserRole).value<voip_calls_info_t*>(); + voip_calls_info_t *other_call_info = other.data(0, Qt::UserRole).value<voip_calls_info_t*>(); + if (!this_call_info || !other_call_info) { + return false; + } + + switch (treeWidget()->sortColumn()) { + case start_time_col_: + return nstime_cmp(&(this_call_info->start_rel_ts), &(other_call_info->start_rel_ts)) < 0; + break; + case stop_time_col_: + return nstime_cmp(&(this_call_info->stop_rel_ts), &(other_call_info->stop_rel_ts)) < 0; + break; + case initial_speaker_col_: + return cmp_address(&(this_call_info->initial_speaker), &(other_call_info->initial_speaker)) < 0; + break; + case packets_col_: + return this_call_info->npackets < other_call_info->npackets; + break; + default: + break; + } + + // Fall back to string comparison + return QTreeWidgetItem::operator <(other); + } + +}; + +VoipCallsDialog::VoipCallsDialog(QWidget *parent, capture_file *cf, bool all_flows) : + QDialog(parent), + ui(new Ui::VoipCallsDialog), + cap_file_(cf) +{ + ui->setupUi(this); + ui->callTreeWidget->sortByColumn(start_time_col_, Qt::AscendingOrder); + + ctx_menu_.addActions(QList<QAction *>() << ui->actionSelect_All); + + prepare_button_ = ui->buttonBox->addButton(tr("Prepare Filter"), QDialogButtonBox::ApplyRole); + sequence_button_ = ui->buttonBox->addButton(tr("Flow Sequence"), QDialogButtonBox::ApplyRole); + player_button_ = ui->buttonBox->addButton(tr("Play Call"), QDialogButtonBox::ApplyRole); + player_button_->setIcon(StockIcon("media-playback-start")); + + // XXX Use recent settings instead + if (parent) { + resize(parent->width() * 4 / 5, parent->height() * 2 / 3); + } + + memset (&tapinfo_, 0, sizeof(tapinfo_)); + tapinfo_.tap_packet = tapPacket; + tapinfo_.tap_draw = tapDraw; + tapinfo_.tap_data = this; + tapinfo_.callsinfos = g_queue_new(); + tapinfo_.h225_cstype = H225_OTHER; + tapinfo_.fs_option = all_flows ? FLOW_ALL : FLOW_ONLY_INVITES; /* flow show option */ + tapinfo_.graph_analysis = sequence_analysis_info_new(); + tapinfo_.graph_analysis->type = SEQ_ANALYSIS_VOIP; + + voip_calls_init_all_taps(&tapinfo_); + + updateWidgets(); + + if (cap_file_) { + tapinfo_.session = cap_file_->epan; + cf_retap_packets(cap_file_); + } +} + +VoipCallsDialog::~VoipCallsDialog() +{ + delete ui; + + voip_calls_remove_all_tap_listeners(&tapinfo_); + sequence_analysis_info_free(tapinfo_.graph_analysis); +} + +void VoipCallsDialog::setCaptureFile(capture_file *cf) +{ + if (!cf) { // We only want to know when the file closes. + voip_calls_remove_all_tap_listeners(&tapinfo_); + cap_file_ = NULL; + tapinfo_.session = NULL; + } + emit captureFileChanged(cap_file_); + updateWidgets(); +} + +void VoipCallsDialog::contextMenuEvent(QContextMenuEvent *event) +{ + ctx_menu_.exec(event->globalPos()); +} + +void VoipCallsDialog::changeEvent(QEvent *event) +{ + if (0 != event) + { + switch (event->type()) + { + case QEvent::LanguageChange: + ui->retranslateUi(this); + break; + default: + break; + } + } + QDialog::changeEvent(event); +} + +//void VoipCallsDialog::tapReset(void *tapinfo_ptr) +//{ +// Q_UNUSED(tapinfo_ptr) +// voip_calls_tapinfo_t *tapinfo = (voip_calls_tapinfo_t *) tapinfo_ptr; +//} + +gboolean VoipCallsDialog::tapPacket(void *tapinfo_ptr, packet_info *pinfo, epan_dissect_t *, const void *data) +{ + Q_UNUSED(tapinfo_ptr) + Q_UNUSED(pinfo) + Q_UNUSED(data) +#ifdef QT_MULTIMEDIAWIDGETS_LIB +// voip_calls_tapinfo_t *tapinfo = (voip_calls_tapinfo_t *) tapinfo_ptr; + // add_rtp_packet for voip player. +// return TRUE; +#endif + return FALSE; +} + +void VoipCallsDialog::tapDraw(void *tapinfo_ptr) +{ + voip_calls_tapinfo_t *tapinfo = (voip_calls_tapinfo_t *) tapinfo_ptr; + + if (!tapinfo || !tapinfo->redraw) { + return; + } + + VoipCallsDialog *voip_calls_dialog = static_cast<VoipCallsDialog *>(tapinfo->tap_data); + if (voip_calls_dialog) { + voip_calls_dialog->updateCalls(); + } +} + +void VoipCallsDialog::updateCalls() +{ + GList *cur_call = g_queue_peek_nth_link(tapinfo_.callsinfos, ui->callTreeWidget->topLevelItemCount()); + ui->callTreeWidget->setSortingEnabled(false); + + // Add any missing items + while (cur_call && cur_call->data) { + voip_calls_info_t *call_info = (voip_calls_info_t*) cur_call->data; + new VoipCallsTreeWidgetItem(ui->callTreeWidget, call_info); + cur_call = g_list_next(cur_call); + } + + // Fill in the tree + QTreeWidgetItemIterator iter(ui->callTreeWidget); + while (*iter) { + VoipCallsTreeWidgetItem *vcti = static_cast<VoipCallsTreeWidgetItem*>(*iter); + vcti->drawData(); + ++iter; + } + + // Resize columns + for (int i = 0; i < ui->callTreeWidget->columnCount(); i++) { + ui->callTreeWidget->resizeColumnToContents(i); + } + + ui->callTreeWidget->setSortingEnabled(true); + + updateWidgets(); +} + +void VoipCallsDialog::updateWidgets() +{ + bool selected = ui->callTreeWidget->selectedItems().count() > 0 ? true : false; + bool have_ga_items = false; + + if (tapinfo_.graph_analysis && tapinfo_.graph_analysis->items) { + have_ga_items = true; + } + + foreach (QMenu *submenu, ctx_menu_.findChildren<QMenu*>()) { + submenu->setEnabled(selected); + } + prepare_button_->setEnabled(selected && have_ga_items); + sequence_button_->setEnabled(selected && have_ga_items); +#if defined(QT_MULTIMEDIAWIDGETS_LIB) && 0 // We don't have a playback dialog yet. + player_button_->setEnabled(selected && have_ga_items); +#else + player_button_->setEnabled(false); + player_button_->setText(tr("No Audio")); +#endif +} + +void VoipCallsDialog::prepareFilter() +{ + if (ui->callTreeWidget->selectedItems().count() < 1 || !tapinfo_.graph_analysis) { + return; + } + + QString filter_str; + QSet<guint16> selected_calls; + + /* Build a new filter based on frame numbers */ + const char *or_prepend = ""; + foreach (QTreeWidgetItem *ti, ui->callTreeWidget->selectedItems()) { + voip_calls_info_t *call_info = ti->data(0, Qt::UserRole).value<voip_calls_info_t*>(); + selected_calls << call_info->call_num; + } + + GList *cur_ga_item = g_queue_peek_nth_link(tapinfo_.graph_analysis->items, 0); + while (cur_ga_item && cur_ga_item->data) { + seq_analysis_item_t *ga_item = (seq_analysis_item_t*) cur_ga_item->data; + if (selected_calls.contains(ga_item->conv_num)) { + filter_str += QString("%1frame.number == %2").arg(or_prepend).arg(ga_item->fd->num); + or_prepend = " or "; + } + cur_ga_item = g_list_next(cur_ga_item); + } + +#if 0 + // XXX The GTK+ UI falls back to building a filter based on protocols if the filter + // length is too long. Leaving this here for the time being in case we need to do + // the same in the Qt UI. + const sip_calls_info_t *sipinfo; + const isup_calls_info_t *isupinfo; + const h323_calls_info_t *h323info; + const h245_address_t *h245_add = NULL; + const gcp_ctx_t* ctx; + + if (filter_length < max_filter_length) { + gtk_editable_insert_text(GTK_EDITABLE(main_display_filter_widget), filter_string_fwd->str, -1, &pos); + } else { + g_string_free(filter_string_fwd, TRUE); + filter_string_fwd = g_string_new(filter_prepend); + + g_string_append_printf(filter_string_fwd, "("); + is_first = TRUE; + /* Build a new filter based on protocol fields */ + lista = g_queue_peek_nth_link(voip_calls_get_info()->callsinfos, 0); + while (lista) { + listinfo = (voip_calls_info_t *)lista->data; + if (listinfo->selected) { + if (!is_first) + g_string_append_printf(filter_string_fwd, " or "); + switch (listinfo->protocol) { + case VOIP_SIP: + sipinfo = (sip_calls_info_t *)listinfo->prot_info; + g_string_append_printf(filter_string_fwd, + "(sip.Call-ID == \"%s\")", + sipinfo->call_identifier + ); + break; + case VOIP_ISUP: + isupinfo = (isup_calls_info_t *)listinfo->prot_info; + g_string_append_printf(filter_string_fwd, + "(isup.cic == %i and frame.number >= %i and frame.number <= %i and mtp3.network_indicator == %i and ((mtp3.dpc == %i) and (mtp3.opc == %i)) or ((mtp3.dpc == %i) and (mtp3.opc == %i)))", + isupinfo->cic, listinfo->start_fd->num, + listinfo->stop_fd->num, + isupinfo->ni, isupinfo->dpc, isupinfo->opc, + isupinfo->opc, isupinfo->dpc + ); + break; + case VOIP_H323: + h323info = (h323_calls_info_t *)listinfo->prot_info; + g_string_append_printf(filter_string_fwd, + "((h225.guid == %s || q931.call_ref == %x:%x || q931.call_ref == %x:%x)", + guid_to_ep_str(&h323info->guid[0]), + (guint8) (h323info->q931_crv & 0x00ff), + (guint8)((h323info->q931_crv & 0xff00)>>8), + (guint8) (h323info->q931_crv2 & 0x00ff), + (guint8)((h323info->q931_crv2 & 0xff00)>>8)); + listb = g_list_first(h323info->h245_list); + while (listb) { + h245_add = (h245_address_t *)listb->data; + g_string_append_printf(filter_string_fwd, + " || (ip.addr == %s && tcp.port == %d && h245)", + ip_to_str((guint8 *)(h245_add->h245_address.data)), h245_add->h245_port); + listb = g_list_next(listb); + } + g_string_append_printf(filter_string_fwd, ")"); + break; + case TEL_H248: + ctx = (gcp_ctx_t *)listinfo->prot_info; + g_string_append_printf(filter_string_fwd, + "(h248.ctx == 0x%x)", ctx->id); + break; + default: + /* placeholder to assure valid display filter expression */ + g_string_append_printf(filter_string_fwd, + "(frame)"); + break; + } + is_first = FALSE; + } + lista = g_list_next(lista); + } + + g_string_append_printf(filter_string_fwd, ")"); + gtk_editable_insert_text(GTK_EDITABLE(main_display_filter_widget), filter_string_fwd->str, -1, &pos); + } +#endif + + emit updateFilter(filter_str); +} + +void VoipCallsDialog::showSequence() +{ + if (!cap_file_) return; + + QSet<guint16> selected_calls; + foreach (QTreeWidgetItem *ti, ui->callTreeWidget->selectedItems()) { + voip_calls_info_t *call_info = ti->data(0, Qt::UserRole).value<voip_calls_info_t*>(); + selected_calls << call_info->call_num; + } + + sequence_analysis_list_sort(tapinfo_.graph_analysis); + GList *cur_ga_item = g_queue_peek_nth_link(tapinfo_.graph_analysis->items, 0); + while (cur_ga_item && cur_ga_item->data) { + seq_analysis_item_t *ga_item = (seq_analysis_item_t*) cur_ga_item->data; + ga_item->display = selected_calls.contains(ga_item->conv_num); + cur_ga_item = g_list_next(cur_ga_item); + } + + SequenceDialog *sequence_dialog = new SequenceDialog(this, cap_file_, tapinfo_.graph_analysis); + // XXX This goes away when we close the VoIP Calls dialog. + connect(sequence_dialog, SIGNAL(goToPacket(int)), + this, SIGNAL(goToPacket(int))); + connect(this, SIGNAL(captureFileChanged(capture_file*)), + sequence_dialog, SLOT(setCaptureFile(capture_file*))); + sequence_dialog->show(); +} + +void VoipCallsDialog::on_callTreeWidget_itemActivated(QTreeWidgetItem *item, int) +{ + voip_calls_info_t *call_info = item->data(0, Qt::UserRole).value<voip_calls_info_t*>(); + if (!call_info) { + return; + } + emit goToPacket(call_info->start_fd->num); +} + +void VoipCallsDialog::on_callTreeWidget_itemSelectionChanged() +{ + updateWidgets(); +} + +void VoipCallsDialog::on_actionSelect_All_triggered() +{ + ui->callTreeWidget->selectAll(); +} + +void VoipCallsDialog::on_buttonBox_clicked(QAbstractButton *button) +{ + if (button == prepare_button_) { + prepareFilter(); + } else if (button == sequence_button_) { + showSequence(); + } +} + +void VoipCallsDialog::on_buttonBox_helpRequested() +{ + wsApp->helpTopicAction(HELP_TELEPHONY_VOIP_CALLS_DIALOG); +} + +/* + * 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: + */ |