/* packet_list.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 "config.h" #include #include #include #include #include #include #include #include "packet_list.h" #include "proto_tree.h" #include "wireshark_application.h" #include "epan/ipproto.h" #include "qt_ui_utils.h" #include "ui/main_statusbar.h" #include "ui/recent.h" #include "ui/recent_utils.h" #include "ui/ui_util.h" #include "wsutil/str_util.h" #include "frame_tvbuff.h" #include #include #include #include #include #include // If we ever add the ability to open multiple capture files we might be // able to use something like QMap to match // capture files against packet lists and models. static PacketList *gbl_cur_packet_list = NULL; const int max_comments_to_fetch_ = 20000000; // Arbitrary guint packet_list_append(column_info *cinfo, frame_data *fdata) { Q_UNUSED(cinfo); if (!gbl_cur_packet_list) return 0; /* fdata should be filled with the stuff we need * strings are built at display time. */ guint visible_pos; visible_pos = gbl_cur_packet_list->packetListModel()->appendPacket(fdata); return visible_pos; } // Copied from ui/gtk/packet_list.c void packet_list_resize_column(gint col) { Q_UNUSED(col) // xxx qtshark // gint col_width; // const gchar *long_str; //g_log(NULL, G_LOG_LEVEL_DEBUG, "FIX: packet_list_resize_column %d", col); // long_str = packet_list_get_widest_column_string(packetlist, col); // if(!long_str || strcmp("",long_str)==0) // /* If we get an empty string leave the width unchanged */ // return; // column = gtk_tree_view_get_column (GTK_TREE_VIEW(packetlist->view), col); // col_width = get_default_col_size (packetlist->view, long_str); // gtk_tree_view_column_set_fixed_width(column, col_width); } void packet_list_select_first_row(void) { if (!gbl_cur_packet_list) return; gbl_cur_packet_list->goFirstPacket(); gbl_cur_packet_list->setFocus(); } void packet_list_select_last_row(void) { if (!gbl_cur_packet_list) return; gbl_cur_packet_list->goLastPacket(); gbl_cur_packet_list->setFocus(); } /* * Given a frame_data structure, scroll to and select the row in the * packet list corresponding to that frame. If there is no such * row, return FALSE, otherwise return TRUE. */ gboolean packet_list_select_row_from_data(frame_data *fdata_needle) { int row = gbl_cur_packet_list->packetListModel()->visibleIndexOf(fdata_needle); if (row >= 0) { gbl_cur_packet_list->setCurrentIndex(gbl_cur_packet_list->packetListModel()->index(row,0)); return TRUE; } return FALSE; } gboolean packet_list_check_end(void) { if (gbl_cur_packet_list) { QScrollBar *sb = gbl_cur_packet_list->verticalScrollBar(); if (sb && sb->isVisible() && sb->value() == sb->maximum()) { return TRUE; } } return FALSE; } void packet_list_clear(void) { if (gbl_cur_packet_list) { gbl_cur_packet_list->clear(); } } void packet_list_enable_color(gboolean enable) { if (gbl_cur_packet_list && gbl_cur_packet_list->packetListModel()) { gbl_cur_packet_list->packetListModel()->setColorEnabled(enable); gbl_cur_packet_list->update(); } } void packet_list_freeze(void) { if (gbl_cur_packet_list) { gbl_cur_packet_list->freeze(); } } void packet_list_thaw(void) { if (gbl_cur_packet_list) { gbl_cur_packet_list->thaw(); } packets_bar_update(); } void packet_list_recreate_visible_rows(void) { if (gbl_cur_packet_list && gbl_cur_packet_list->packetListModel()) { gbl_cur_packet_list->packetListModel()->recreateVisibleRows(); } } frame_data * packet_list_get_row_data(gint row) { if (gbl_cur_packet_list && gbl_cur_packet_list->packetListModel()) { return gbl_cur_packet_list->packetListModel()->getRowFdata(row); } return NULL; } void packet_list_moveto_end(void) { if (gbl_cur_packet_list) gbl_cur_packet_list->goLastPacket(); } /* Redraw the packet list *and* currently-selected detail */ void packet_list_queue_draw(void) { if (gbl_cur_packet_list) gbl_cur_packet_list->updateAll(); } void packet_list_recent_write_all(FILE *rf) { if (!gbl_cur_packet_list) return; gbl_cur_packet_list->writeRecent(rf); } #define MIN_COL_WIDTH_STR "...." PacketList::PacketList(QWidget *parent) : QTreeView(parent), proto_tree_(NULL), byte_view_tab_(NULL), cap_file_(NULL), decode_as_(NULL), ctx_column_(-1) { QMenu *submenu, *subsubmenu; setItemsExpandable(FALSE); setRootIsDecorated(FALSE); setSortingEnabled(TRUE); setUniformRowHeights(TRUE); setAccessibleName("Packet list"); setItemDelegateForColumn(0, &related_packet_delegate_); packet_list_model_ = new PacketListModel(this, cap_file_); setModel(packet_list_model_); packet_list_model_->setColorEnabled(recent.packet_list_colorize); // XXX We might want to reimplement setParent() and fill in the context // menu there. ctx_menu_.addAction(window()->findChild("actionEditMarkPacket")); ctx_menu_.addAction(window()->findChild("actionEditIgnorePacket")); ctx_menu_.addAction(window()->findChild("actionEditSetTimeReference")); ctx_menu_.addAction(window()->findChild("actionEditTimeShift")); ctx_menu_.addAction(window()->findChild("actionEditPacketComment")); ctx_menu_.addSeparator(); submenu = new QMenu(tr("Follow...")); ctx_menu_.addMenu(submenu); submenu->addAction(window()->findChild("actionAnalyzeFollowTCPStream")); submenu->addAction(window()->findChild("actionAnalyzeFollowUDPStream")); submenu->addAction(window()->findChild("actionAnalyzeFollowSSLStream")); filter_actions_ << submenu->actions(); // " \n" // " \n" // " \n" submenu = new QMenu(tr("SCTP")); ctx_menu_.addMenu(submenu); submenu->addAction(window()->findChild("actionSCTPAnalyseThisAssociation")); submenu->addAction(window()->findChild("actionSCTPShowAllAssociations")); submenu->addAction(window()->findChild("actionSCTPFilterThisAssociation")); filter_actions_ << submenu->actions(); ctx_menu_.addSeparator(); // " \n" ctx_menu_.addSeparator(); submenu = new QMenu(tr("Apply as Filter")); ctx_menu_.addMenu(submenu); submenu->addAction(window()->findChild("actionAnalyzeAAFSelected")); submenu->addAction(window()->findChild("actionAnalyzeAAFNotSelected")); submenu->addAction(window()->findChild("actionAnalyzeAAFAndSelected")); submenu->addAction(window()->findChild("actionAnalyzeAAFOrSelected")); submenu->addAction(window()->findChild("actionAnalyzeAAFAndNotSelected")); submenu->addAction(window()->findChild("actionAnalyzeAAFOrNotSelected")); filter_actions_ << submenu->actions(); submenu = new QMenu(tr("Prepare a Filter")); ctx_menu_.addMenu(submenu); submenu->addAction(window()->findChild("actionAnalyzePAFSelected")); submenu->addAction(window()->findChild("actionAnalyzePAFNotSelected")); submenu->addAction(window()->findChild("actionAnalyzePAFAndSelected")); submenu->addAction(window()->findChild("actionAnalyzePAFOrSelected")); submenu->addAction(window()->findChild("actionAnalyzePAFAndNotSelected")); submenu->addAction(window()->findChild("actionAnalyzePAFOrNotSelected")); filter_actions_ << submenu->actions(); submenu = new QMenu(tr("Colorize with Filter")); // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" ctx_menu_.addSeparator(); // " \n" submenu = new QMenu(tr("Copy")); ctx_menu_.addMenu(submenu); // " \n" // " \n" submenu->addAction(window()->findChild("actionEditCopyAsFilter")); filter_actions_ << window()->findChild("actionEditCopyAsFilter"); submenu->addSeparator(); subsubmenu = new QMenu(tr("Bytes")); submenu->addMenu(subsubmenu); // " \n" // " \n" // " \n" ctx_menu_.addSeparator(); // " \n" // " \n" ctx_menu_.addSeparator(); // " \n" decode_as_ = window()->findChild("actionAnalyzeDecodeAs"); ctx_menu_.addAction(decode_as_); // " \n" // " \n" g_assert(gbl_cur_packet_list == NULL); gbl_cur_packet_list = this; } void PacketList::setProtoTree (ProtoTree *proto_tree) { proto_tree_ = proto_tree; connect(proto_tree_, SIGNAL(goToFrame(int)), this, SLOT(goToPacket(int))); connect(proto_tree_, SIGNAL(relatedFrame(int)), this, SLOT(addRelatedFrame(int))); } void PacketList::setByteViewTab (ByteViewTab *byte_view_tab) { byte_view_tab_ = byte_view_tab; connect(proto_tree_, SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)), byte_view_tab_, SLOT(protoTreeItemChanged(QTreeWidgetItem*))); } PacketListModel *PacketList::packetListModel() const { return packet_list_model_; } void PacketList::showEvent (QShowEvent *event) { Q_UNUSED(event); for (int i = 0; i < prefs.num_cols; i++) { int fmt, col_width; const char *long_str; fmt = get_column_format(i); long_str = get_column_width_string(fmt, i); if (long_str) { col_width = wsApp->monospaceTextSize(long_str); } else { col_width = wsApp->monospaceTextSize(MIN_COL_WIDTH_STR); } setColumnWidth(i, col_width); } setColumnVisibility(); } void PacketList::selectionChanged (const QItemSelection & selected, const QItemSelection & deselected) { QTreeView::selectionChanged(selected, deselected); if (!cap_file_) return; int row = selected.first().top(); cf_select_packet(cap_file_, row); related_packet_delegate_.clear(); emit packetSelectionChanged(); if (!cap_file_->edt) return; if (proto_tree_ && cap_file_->edt->tree) { proto_tree_->fillProtocolTree(cap_file_->edt->tree); packet_info *pi = &cap_file_->edt->pi; conversation_t *conv = find_conversation(pi->fd->num, &pi->src, &pi->dst, pi->ptype, pi->srcport, pi->destport, 0); if (conv) { related_packet_delegate_.setConversationSpan(conv->setup_frame, conv->last_frame); } viewport()->update(); } if (byte_view_tab_) { GSList *src_le; struct data_source *source; byte_view_tab_->clear(); for (src_le = cap_file_->edt->pi.data_src; src_le != NULL; src_le = src_le->next) { source = (struct data_source *)src_le->data; byte_view_tab_->addTab(get_data_source_name(source), get_data_source_tvb(source), cap_file_->edt->tree, proto_tree_, (packet_char_enc)cap_file_->current_frame->flags.encoding); } byte_view_tab_->setCurrentIndex(0); } } void PacketList::contextMenuEvent(QContextMenuEvent *event) { bool fa_enabled = filter_actions_[0]->isEnabled(); QAction *act; gboolean is_tcp = FALSE, is_udp = FALSE; /* walk the list of a available protocols in the packet to see what we have */ if ((cap_file_ != NULL) && (cap_file_->edt != NULL)) { proto_get_frame_protocols(cap_file_->edt->pi.layers, NULL, &is_tcp, &is_udp, NULL, NULL); } foreach (act, filter_actions_) { act->setEnabled(true); // check SCTP if (act->objectName().contains("SCTP")) { if ((cap_file_ != NULL) && (cap_file_->edt != NULL) && (cap_file_->edt->pi.ipproto == IP_PROTO_SCTP)) { act->setEnabled(true); } else { act->setEnabled(false); } } // check follow stream if (act->text().contains("TCP")) { act->setEnabled(is_tcp); } if (act->text().contains("UDP")) { act->setEnabled(is_udp); } if ((cap_file_ != NULL) && act->text().contains("SSL")) { if (epan_dissect_packet_contains_field(cap_file_->edt, "ssl")) { act->setEnabled(true); } else { act->setEnabled(false); } } } decode_as_->setData(qVariantFromValue(true)); ctx_column_ = columnAt(event->x()); ctx_menu_.exec(event->globalPos()); ctx_column_ = -1; decode_as_->setData(QVariant()); foreach (act, filter_actions_) { act->setEnabled(fa_enabled); } } void PacketList::markFramesReady() { packets_bar_update(); updateAll(); } void PacketList::setFrameMark(gboolean set, frame_data *fdata) { if (set) cf_mark_frame(cap_file_, fdata); else cf_unmark_frame(cap_file_, fdata); } void PacketList::setFrameIgnore(gboolean set, frame_data *fdata) { if (set) cf_ignore_frame(cap_file_, fdata); else cf_unignore_frame(cap_file_, fdata); } void PacketList::setFrameReftime(gboolean set, frame_data *fdata) { if (!fdata || !cap_file_) return; if (set) { fdata->flags.ref_time=1; cap_file_->ref_time_count++; } else { fdata->flags.ref_time=0; cap_file_->ref_time_count--; } cf_reftime_packets(cap_file_); if (!fdata->flags.ref_time && !fdata->flags.passed_dfilter) { cap_file_->displayed_count--; packet_list_model_->recreateVisibleRows(); } updateAll(); } void PacketList::setColumnVisibility() { for (int i = 0; i < prefs.num_cols; i++) { setColumnHidden(i, get_column_visible(i) ? false : true); } } // Redraw the packet list and detail void PacketList::updateAll() { update(); if (!cap_file_) return; if (selectedIndexes().length() > 0) { cf_select_packet(cap_file_, selectedIndexes()[0].row()); } if (cap_file_->edt && cap_file_->edt->tree) { proto_tree_->fillProtocolTree(cap_file_->edt->tree); } packet_list_model_->resetColumns(); } void PacketList::freeze() { setUpdatesEnabled(false); setModel(NULL); } void PacketList::thaw() { setModel(packet_list_model_); setUpdatesEnabled(true); setColumnVisibility(); } void PacketList::clear() { // packet_history_clear(); related_packet_delegate_.clear(); packet_list_model_->clear(); proto_tree_->clear(); byte_view_tab_->clear(); /* XXX is this correct in all cases? * Reset the sort column, use packetlist as model in case the list is frozen. */ sortByColumn(0, Qt::AscendingOrder); setColumnVisibility(); } void PacketList::writeRecent(FILE *rf) { gint col, width, col_fmt; gchar xalign; fprintf (rf, "%s:", RECENT_KEY_COL_WIDTH); for (col = 0; col < packet_list_model_->columnCount(); col++) { if (col > 0) { fprintf (rf, ","); } col_fmt = get_column_format(col); if (col_fmt == COL_CUSTOM) { fprintf (rf, " %%Cus:%s,", get_column_custom_field(col)); } else { fprintf (rf, " %s,", col_format_to_string(col_fmt)); } width = columnWidth(col); xalign = recent_get_column_xalign (col); if (width == 0) { /* We have not initialized the packet list yet, use old values */ width = recent_get_column_width (col); } fprintf (rf, " %d", width); if (xalign != COLUMN_XALIGN_DEFAULT) { fprintf (rf, ":%c", xalign); } } fprintf (rf, "\n"); } bool PacketList::contextMenuActive() { return ctx_column_ >= 0 ? true : false; } QString &PacketList::getFilterFromRowAndColumn() { frame_data *fdata; QString &filter = *new QString(); int row = currentIndex().row(); if (!cap_file_ || !packet_list_model_ || ctx_column_ < 0 || ctx_column_ >= cap_file_->cinfo.num_cols) return filter; fdata = packet_list_model_->getRowFdata(row); if (fdata != NULL) { epan_dissect_t edt; if (!cf_read_record(cap_file_, fdata)) return filter; /* error reading the record */ /* proto tree, visible. We need a proto tree if there's custom columns */ epan_dissect_init(&edt, cap_file_->epan, have_custom_cols(&cap_file_->cinfo), FALSE); col_custom_prime_edt(&edt, &cap_file_->cinfo); epan_dissect_run(&edt, cap_file_->cd_t, &cap_file_->phdr, frame_tvbuff_new_buffer(fdata, &cap_file_->buf), fdata, &cap_file_->cinfo); epan_dissect_fill_in_columns(&edt, TRUE, TRUE); if ((cap_file_->cinfo.col_custom_occurrence[ctx_column_]) || (strchr (cap_file_->cinfo.col_expr.col_expr_val[ctx_column_], ',') == NULL)) { /* Only construct the filter when a single occurrence is displayed * otherwise we might end up with a filter like "ip.proto==1,6". * * Or do we want to be able to filter on multiple occurrences so that * the filter might be calculated as "ip.proto==1 && ip.proto==6" * instead? */ if (strlen(cap_file_->cinfo.col_expr.col_expr[ctx_column_]) != 0 && strlen(cap_file_->cinfo.col_expr.col_expr_val[ctx_column_]) != 0) { /* leak a little but safer than ep_ here */ if (cap_file_->cinfo.col_fmt[ctx_column_] == COL_CUSTOM) { header_field_info *hfi = proto_registrar_get_byname(cap_file_->cinfo.col_custom_field[ctx_column_]); if (hfi->parent == -1) { /* Protocol only */ filter.append(cap_file_->cinfo.col_expr.col_expr[ctx_column_]); } else if (hfi->type == FT_STRING) { /* Custom string, add quotes */ filter.append(QString("%1 == \"%2\"") .arg(cap_file_->cinfo.col_expr.col_expr[ctx_column_]) .arg(cap_file_->cinfo.col_expr.col_expr_val[ctx_column_])); } } if (filter.isEmpty()) { filter.append(QString("%1 == %2") .arg(cap_file_->cinfo.col_expr.col_expr[ctx_column_]) .arg(cap_file_->cinfo.col_expr.col_expr_val[ctx_column_])); } } } epan_dissect_cleanup(&edt); } return filter; } QString PacketList::packetComment() { int row = currentIndex().row(); frame_data *fdata; char *pkt_comment; if (!cap_file_ || !packet_list_model_) return NULL; fdata = packet_list_model_->getRowFdata(row); if (!fdata) return NULL; pkt_comment = cf_get_comment(cap_file_, fdata); return QString(pkt_comment); /* XXX, g_free(pkt_comment) */ } void PacketList::setPacketComment(QString new_comment) { int row = currentIndex().row(); frame_data *fdata; gchar *new_packet_comment = new_comment.toUtf8().data(); if (!cap_file_ || !packet_list_model_) return; fdata = packet_list_model_->getRowFdata(row); if (!fdata) return; /* Check if we are clearing the comment */ if(new_comment.isEmpty()) { new_packet_comment = NULL; } cf_set_user_packet_comment(cap_file_, fdata, new_packet_comment); updateAll(); } QString PacketList::allPacketComments() { guint32 framenum; frame_data *fdata; QString buf_str; if (!cap_file_) return buf_str; for (framenum = 1; framenum <= cap_file_->count ; framenum++) { fdata = frame_data_sequence_find(cap_file_->frames, framenum); char *pkt_comment = cf_get_comment(cap_file_, fdata); if (pkt_comment) { buf_str.append(QString(tr("Frame %1: %2 \n\n")).arg(framenum).arg(pkt_comment)); g_free(pkt_comment); } if (buf_str.length() > max_comments_to_fetch_) { buf_str.append(QString(tr("[ Comment text exceeds %1. Stopping. ]")) .arg(format_size(max_comments_to_fetch_, format_size_unit_bytes|format_size_prefix_si))); return buf_str; } } return buf_str; } // Slots void PacketList::setCaptureFile(capture_file *cf) { cap_file_ = cf; packet_list_model_->setCaptureFile(cf); } void PacketList::goNextPacket(void) { setCurrentIndex(moveCursor(MoveDown, Qt::NoModifier)); } void PacketList::goPreviousPacket(void) { setCurrentIndex(moveCursor(MoveUp, Qt::NoModifier)); } void PacketList::goFirstPacket(void) { setCurrentIndex(moveCursor(MoveHome, Qt::NoModifier)); } void PacketList::goLastPacket(void) { setCurrentIndex(moveCursor(MoveEnd, Qt::NoModifier)); } // XXX We can jump to the wrong packet if a display filter is applied void PacketList::goToPacket(int packet) { int row = packet_list_model_->packetNumberToRow(packet); if (row > 0) { setCurrentIndex(packet_list_model_->index(row, 0)); } } void PacketList::markFrame() { int row = currentIndex().row(); frame_data *fdata; if (!cap_file_ || !packet_list_model_) return; fdata = packet_list_model_->getRowFdata(row); setFrameMark(!fdata->flags.marked, fdata); markFramesReady(); } void PacketList::markAllDisplayedFrames(bool set) { guint32 framenum; frame_data *fdata; if (!cap_file_ || !packet_list_model_) return; for (framenum = 1; framenum <= cap_file_->count; framenum++) { fdata = frame_data_sequence_find(cap_file_->frames, framenum); if (fdata->flags.passed_dfilter) setFrameMark(set, fdata); } markFramesReady(); } void PacketList::ignoreFrame() { int row = currentIndex().row(); frame_data *fdata; if (!cap_file_ || !packet_list_model_) return; fdata = packet_list_model_->getRowFdata(row); setFrameIgnore(!fdata->flags.ignored, fdata); emit packetDissectionChanged(); } void PacketList::ignoreAllDisplayedFrames(bool set) { guint32 framenum; frame_data *fdata; if (!cap_file_ || !packet_list_model_) return; for (framenum = 1; framenum <= cap_file_->count; framenum++) { fdata = frame_data_sequence_find(cap_file_->frames, framenum); if (!set || fdata->flags.passed_dfilter) setFrameIgnore(set, fdata); } emit packetDissectionChanged(); } void PacketList::setTimeReference() { if (!cap_file_) return; if (cap_file_->current_frame) { if(recent.gui_time_format != TS_RELATIVE && cap_file_->current_frame->flags.ref_time==0) { int ret = QMessageBox::question( this, tr("Change Time Display Format?"), tr("Time References don't work well with the currently selected Time Display Format.\n" "Do you want to switch to \"Seconds Since Beginning of Capture\" now?"), QMessageBox::Yes | QMessageBox::No ); if (ret == QMessageBox::Yes) { timestamp_set_type(TS_RELATIVE); recent.gui_time_format = TS_RELATIVE; cf_timestamp_auto_precision(cap_file_); } } else { setFrameReftime(!cap_file_->current_frame->flags.ref_time, cap_file_->current_frame); } } updateAll(); } void PacketList::unsetAllTimeReferences() { if (!cap_file_) return; /* XXX: we might need a progressbar here */ guint32 framenum; frame_data *fdata; for (framenum = 1; framenum <= cap_file_->count && cap_file_->ref_time_count > 0; framenum++) { fdata = frame_data_sequence_find(cap_file_->frames, framenum); if (fdata->flags.ref_time == 1) { setFrameReftime(FALSE, fdata); } } updateAll(); } void PacketList::addRelatedFrame(int related_frame) { related_packet_delegate_.addRelatedFrame(related_frame); } /* * 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: */