/* voip_calls_dialog.cpp * * Wireshark - Network traffic analyzer * By Gerald Combs * 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 #include "file.h" #include "epan/addr_resolv.h" #include "epan/dissectors/packet-h225.h" #include "ui/utf8_entities.h" #include "qt_ui_utils.h" #include "sequence_dialog.h" #include "stock_icon.h" #include "wireshark_application.h" #include #include // 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(); 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_, address_to_display_qstring(&(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 *other_call_info = other.data(0, Qt::UserRole).value(); 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, CaptureFile &cf, bool all_flows) : WiresharkDialog(parent, cf), ui(new Ui::VoipCallsDialog), parent_(parent) { ui->setupUi(this); ui->callTreeWidget->sortByColumn(start_time_col_, Qt::AscendingOrder); setWindowSubtitle(all_flows ? tr("SIP Flows") : tr("VoIP Calls")); ctx_menu_.addActions(QList() << 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 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_.isValid()) { tapinfo_.session = cap_file_.capFile()->epan; cap_file_.delayedRetapPackets(); } } VoipCallsDialog::~VoipCallsDialog() { delete ui; voip_calls_remove_all_tap_listeners(&tapinfo_); sequence_analysis_info_free(tapinfo_.graph_analysis); } void VoipCallsDialog::captureFileClosing() { voip_calls_remove_all_tap_listeners(&tapinfo_); tapinfo_.session = NULL; WiresharkDialog::captureFileClosing(); } 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 *) //{ // voip_calls_tapinfo_t *tapinfo = (voip_calls_tapinfo_t *) tapinfo_ptr; //} gboolean VoipCallsDialog::tapPacket(void *, packet_info *, epan_dissect_t *, const void *) { #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(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(*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()) { 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 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(); 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; char *guid_str; 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; guid_str = guid_to_str(NULL, &h323info->guid[0]); g_string_append_printf(filter_string_fwd, "((h225.guid == %s || q931.call_ref == %x:%x || q931.call_ref == %x:%x)", guid_str, (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); wmem_free(NULL, guid_str); while (listb) { h245_add = (h245_address_t *)listb->data; g_string_append_printf(filter_string_fwd, " || (ip.addr == %s && tcp.port == %d && h245)", address_to_qstring(&h245_add->h245_address), 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 (file_closed_) return; QSet selected_calls; foreach (QTreeWidgetItem *ti, ui->callTreeWidget->selectedItems()) { voip_calls_info_t *call_info = ti->data(0, Qt::UserRole).value(); 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(parent_, 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))); sequence_dialog->show(); } void VoipCallsDialog::on_callTreeWidget_itemActivated(QTreeWidgetItem *item, int) { voip_calls_info_t *call_info = item->data(0, Qt::UserRole).value(); 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: */