1 /* follow_stream_dialog.cpp
3 * Wireshark - Network traffic analyzer
4 * By Gerald Combs <gerald@wireshark.org>
5 * Copyright 1998 Gerald Combs
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22 #include "follow_stream_dialog.h"
23 #include "ui_follow_stream_dialog.h"
25 #include "main_window.h"
26 #include "wireshark_application.h"
28 #include "epan/follow.h"
29 #include "epan/dissectors/packet-tcp.h"
30 #include "epan/prefs.h"
31 #include "epan/addr_resolv.h"
32 #include "epan/charsets.h"
33 #include "epan/epan_dissect.h"
36 #include "ui/alert_box.h"
37 #include "ui/simple_dialog.h"
38 #include "ui/utf8_entities.h"
40 #include "wsutil/tempfile.h"
41 #include "wsutil/file_util.h"
42 #include "wsutil/str_util.h"
44 #include "ws_symbol_export.h"
46 #include "color_utils.h"
48 #include "version_info.h"
50 #include "ui/follow.h"
52 #include "qt_ui_utils.h"
55 #include <QMessageBox>
56 #include <QPrintDialog>
59 #include <QTextStream>
62 // - Instead of calling QMessageBox, display the error message in the text
63 // box and disable the appropriate controls.
64 // - Add stream number for UDP.
65 // - Draw text by hand similar to ByteViewText. This would let us add
66 // extra information, e.g. a timestamp column and get rid of the data
69 FollowStreamDialog::FollowStreamDialog(QWidget *parent, follow_type_t type, capture_file *cf) :
71 ui(new Ui::FollowStreamDialog),
79 setAttribute(Qt::WA_DeleteOnClose, true);
80 memset(&follow_info_, 0, sizeof(follow_info_));
82 ui->teStreamContent->installEventFilter(this);
84 // XXX Use recent settings instead
86 resize(parent->width() * 2 / 3, parent->height());
89 QComboBox *cbcs = ui->cbCharset;
90 cbcs->blockSignals(true);
91 cbcs->addItem(tr("ASCII"), SHOW_ASCII);
92 cbcs->addItem(tr("C Arrays"), SHOW_CARRAY);
93 cbcs->addItem(tr("EBCDIC"), SHOW_EBCDIC);
94 cbcs->addItem(tr("Hex Dump"), SHOW_HEXDUMP);
95 cbcs->addItem(tr("UTF-8"), SHOW_RAW);
96 cbcs->addItem(tr("YAML"), SHOW_YAML);
97 cbcs->blockSignals(false);
99 b_filter_out_ = ui->buttonBox->addButton(tr("Hide this stream"), QDialogButtonBox::ActionRole);
100 connect(b_filter_out_, SIGNAL(clicked()), this, SLOT(filterOut()));
102 b_print_ = ui->buttonBox->addButton(tr("Print"), QDialogButtonBox::ActionRole);
103 connect(b_print_, SIGNAL(clicked()), this, SLOT(printStream()));
105 b_save_ = ui->buttonBox->addButton(tr("Save as..."), QDialogButtonBox::ActionRole);
106 connect(b_save_, SIGNAL(clicked()), this, SLOT(saveAs()));
108 connect(ui->buttonBox, SIGNAL(helpRequested()), this, SLOT(helpButton()));
109 connect(ui->teStreamContent, SIGNAL(mouseMovedToTextCursorPosition(int)),
110 this, SLOT(fillHintLabel(int)));
111 connect(ui->teStreamContent, SIGNAL(mouseClickedOnTextCursorPosition(int)),
112 this, SLOT(goToPacketForTextPos(int)));
117 FollowStreamDialog::~FollowStreamDialog()
120 resetStream(); // Frees payload
123 void FollowStreamDialog::printStream()
125 #ifndef QT_NO_PRINTER
126 QPrinter printer(QPrinter::HighResolution);
127 QPrintDialog dialog(&printer, this);
128 if ( dialog.exec() == QDialog::Accepted)
129 ui->teStreamContent->print(&printer);
133 void FollowStreamDialog::fillHintLabel(int text_pos)
139 QMap<int, guint32>::iterator it = text_pos_to_packet_.upperBound(text_pos);
140 if (it != text_pos_to_packet_.end()) {
146 hint = QString(tr("Packet %1. ")).arg(pkt);
149 hint += tr("%Ln <span style=\"color: %1; background-color:%2\">client</span> pkt(s), ", "", client_packet_count_)
150 .arg(ColorUtils::fromColorT(prefs.st_client_fg).name())
151 .arg(ColorUtils::fromColorT(prefs.st_client_bg).name())
152 + tr("%Ln <span style=\"color: %1; background-color:%2\">server</span> pkt(s), ", "", server_packet_count_)
153 .arg(ColorUtils::fromColorT(prefs.st_server_fg).name())
154 .arg(ColorUtils::fromColorT(prefs.st_server_bg).name())
155 + tr("%Ln turn(s).", "", turns_);
158 hint.append(QString(tr(" Click to select.")));
161 hint.prepend("<small><i>");
162 hint.append("</i></small>");
163 ui->hintLabel->setText(hint);
166 void FollowStreamDialog::goToPacketForTextPos(int text_pos)
174 QMap<int, guint32>::iterator it = text_pos_to_packet_.upperBound(text_pos);
175 if (it != text_pos_to_packet_.end()) {
181 emit goToPacket(pkt);
185 void FollowStreamDialog::updateWidgets(bool enable)
189 ui->streamNumberSpinBox->setToolTip(QString());
190 ui->streamNumberLabel->setToolTip(QString());
192 ui->teStreamContent->setEnabled(enable);
193 ui->cbDirections->setEnabled(enable);
194 ui->cbCharset->setEnabled(enable);
195 ui->streamNumberSpinBox->setEnabled(enable);
196 ui->leFind->setEnabled(enable);
197 ui->bFind->setEnabled(enable);
198 b_filter_out_->setEnabled(enable);
201 void FollowStreamDialog::findText(bool go_back)
203 if (ui->leFind->text().isEmpty()) return;
205 bool found = ui->teStreamContent->find(ui->leFind->text());
208 ui->teStreamContent->setFocus();
209 } else if (go_back) {
210 ui->teStreamContent->moveCursor(QTextCursor::Start);
215 void FollowStreamDialog::saveAs()
217 QString file_name = QFileDialog::getSaveFileName(this, tr("Wireshark: Save stream content as"));
218 file_.setFileName(file_name);
219 file_.open( QIODevice::WriteOnly );
220 QTextStream out(&file_);
226 if (follow_info_.show_type != SHOW_RAW)
228 out << ui->teStreamContent->toPlainText();
236 void FollowStreamDialog::helpButton()
238 wsApp->helpTopicAction(HELP_FOLLOW_STREAM_DIALOG);
241 void FollowStreamDialog::filterOut()
244 emit updateFilter(filter_out_filter_, TRUE);
249 void FollowStreamDialog::on_cbDirections_currentIndexChanged(int index)
254 follow_info_.show_stream = BOTH_HOSTS;
257 follow_info_.show_stream = FROM_SERVER;
260 follow_info_.show_stream = FROM_CLIENT;
269 void FollowStreamDialog::on_cbCharset_currentIndexChanged(int index)
271 if (index < 0) return;
272 follow_info_.show_type = static_cast<show_type_t>(ui->cbCharset->itemData(index).toInt());
276 void FollowStreamDialog::on_bFind_clicked()
281 void FollowStreamDialog::on_leFind_returnPressed()
286 void FollowStreamDialog::on_streamNumberSpinBox_valueChanged(int stream_num)
288 if (stream_num >= 0) {
289 updateWidgets(false);
290 follow_tcp_index(stream_num);
291 follow(QString(), true);
296 // Not sure why we have to do this manually.
297 void FollowStreamDialog::on_buttonBox_rejected()
302 void FollowStreamDialog::removeStreamControls()
304 ui->horizontalLayout->removeItem(ui->streamNumberSpacer);
305 ui->streamNumberLabel->setVisible(false);
306 ui->streamNumberSpinBox->setVisible(false);
309 void FollowStreamDialog::resetStream()
313 filter_out_filter_.clear();
314 text_pos_to_packet_.clear();
315 if (!data_out_filename_.isEmpty()) {
316 ws_unlink(data_out_filename_.toUtf8().constData());
319 fclose(data_out_file);
320 data_out_file = NULL;
322 for (cur = follow_info_.payload; cur; cur = g_list_next(cur)) {
325 g_list_free(follow_info_.payload);
326 follow_info_.payload = NULL;
330 FollowStreamDialog::readStream()
332 ui->teStreamContent->clear();
336 client_buffer_count_ = 0;
337 server_buffer_count_ = 0;
338 client_packet_count_ = 0;
339 server_packet_count_ = 0;
343 switch(follow_type_) {
346 ret = readTcpStream();
350 ret = readUdpStream();
354 ret = readSslStream();
358 g_assert_not_reached();
359 ret = (frs_return_t)0;
362 ui->teStreamContent->moveCursor(QTextCursor::Start);
366 //Copy from ui/gtk/follow_udp.c
368 udp_queue_packet_data(void *tapdata, packet_info *pinfo,
369 epan_dissect_t *edt, const void *data)
373 follow_record_t *follow_record;
374 follow_info_t *follow_info = (follow_info_t *)tapdata;
375 tvbuff_t *next_tvb = (tvbuff_t *)data;
377 follow_record = g_new(follow_record_t,1);
379 follow_record->data = g_byte_array_sized_new(tvb_length(next_tvb));
380 follow_record->data = g_byte_array_append(follow_record->data,
381 tvb_get_ptr(next_tvb, 0, -1),
382 tvb_length(next_tvb));
383 follow_record->packet_num = pinfo->fd->num;
385 if (follow_info->client_port == 0) {
386 follow_info->client_port = pinfo->srcport;
387 copy_address(&follow_info->client_ip, &pinfo->src);
390 if (addresses_equal(&follow_info->client_ip, &pinfo->src) && follow_info->client_port == pinfo->srcport)
391 follow_record->is_server = FALSE;
393 follow_record->is_server = TRUE;
395 /* update stream counter */
396 follow_info->bytes_written[follow_record->is_server] += follow_record->data->len;
398 follow_info->payload = g_list_append(follow_info->payload, follow_record);
402 //Copy from ui/gtk/follow_ssl.c
404 ssl_queue_packet_data(void *tapdata, packet_info *pinfo, epan_dissect_t *edt, const void *ssl)
408 follow_info_t * follow_info = (follow_info_t*) tapdata;
409 SslDecryptedRecord * rec = NULL;
410 SslDataInfo * appl_data = NULL;
411 int proto_ssl = GPOINTER_TO_INT(ssl);
412 SslPacketInfo * pi = NULL;
413 show_stream_t from = FROM_CLIENT;
415 /* Skip packets without decrypted payload data. */
416 pi = (SslPacketInfo*) p_get_proto_data(wmem_file_scope(), pinfo, proto_ssl, 0);
417 if (!pi || !pi->appl_data) return 0;
419 /* Compute the packet's sender. */
420 if (follow_info->client_port == 0) {
421 follow_info->client_port = pinfo->srcport;
422 copy_address(&follow_info->client_ip, &pinfo->src);
424 if (ADDRESSES_EQUAL(&follow_info->client_ip, &pinfo->src) &&
425 follow_info->client_port == pinfo->srcport) {
431 for (appl_data = pi->appl_data; appl_data != NULL; appl_data = appl_data->next) {
433 /* TCP segments that contain the end of two or more SSL PDUs will be
434 queued to SSL taps for each of those PDUs. Therefore a single
435 packet could be processed by this SSL tap listener multiple times.
436 The following test handles that scenario by treating the
437 follow_info->bytes_written[] values as the next expected
438 appl_data->seq. Any appl_data instances that fall below that have
439 already been processed and must be skipped. */
440 if (appl_data->seq < follow_info->bytes_written[from]) continue;
442 /* Allocate a SslDecryptedRecord to hold the current appl_data
443 instance's decrypted data. Even though it would be possible to
444 consolidate multiple appl_data instances into a single rec, it is
445 beneficial to use a one-to-one mapping. This affords the Follow
446 Stream dialog view modes (ASCII, EBCDIC, Hex Dump, C Arrays, Raw)
447 the opportunity to accurately reflect SSL PDU boundaries. Currently
448 the Hex Dump view does by starting a new line, and the C Arrays
449 view does by starting a new array declaration. */
450 rec = (SslDecryptedRecord*) g_malloc(sizeof(SslDecryptedRecord) + appl_data->plain_data.data_len);
451 rec->is_from_server = from == FROM_SERVER;
452 rec->packet_num = pinfo->fd->num;
453 rec->data.data = (guchar*) (rec + 1);
454 rec->data.data_len = appl_data->plain_data.data_len;
455 memcpy(rec->data.data, appl_data->plain_data.data, appl_data->plain_data.data_len);
457 /* Append the record to the follow_info structure. */
458 follow_info->payload = g_list_append(follow_info->payload, rec);
459 follow_info->bytes_written[from] += rec->data.data_len;
466 * XXX - the routine pointed to by "print_line_fcn_p" doesn't get handed lines,
467 * it gets handed bufferfuls. That's fine for "follow_write_raw()"
468 * and "follow_add_to_gtk_text()", but, as "follow_print_text()" calls
469 * the "print_line()" routine from "print.c", and as that routine might
470 * genuinely expect to be handed a line (if, for example, it's using
471 * some OS or desktop environment's printing API, and that API expects
472 * to be handed lines), "follow_print_text()" should probably accumulate
473 * lines in a buffer and hand them "print_line()". (If there's a
474 * complete line in a buffer - i.e., there's nothing of the line in
475 * the previous buffer or the next buffer - it can just hand that to
476 * "print_line()" after filtering out non-printables, as an
479 * This might or might not be the reason why C arrays display
480 * correctly but get extra blank lines very other line when printed.
483 FollowStreamDialog::readSslStream()
485 guint32 global_client_pos = 0, global_server_pos = 0;
486 guint32 * global_pos;
488 frs_return_t frs_return;
490 for (cur = follow_info_.payload; cur; cur = g_list_next(cur)) {
491 SslDecryptedRecord * rec = (SslDecryptedRecord*) cur->data;
492 gboolean include_rec = FALSE;
494 if (rec->is_from_server) {
495 global_pos = &global_server_pos;
496 include_rec = (follow_info_.show_stream == BOTH_HOSTS) ||
497 (follow_info_.show_stream == FROM_SERVER);
499 global_pos = &global_client_pos;
500 include_rec = (follow_info_.show_stream == BOTH_HOSTS) ||
501 (follow_info_.show_stream == FROM_CLIENT);
505 size_t nchars = rec->data.data_len;
506 gchar *buffer = (gchar *)g_memdup(rec->data.data, (guint) nchars);
508 frs_return = showBuffer(buffer, nchars,
509 rec->is_from_server, rec->packet_num, global_pos);
511 if (frs_return == FRS_PRINT_ERROR)
520 FollowStreamDialog::followStream()
522 follow_stats_t stats;
524 follow_info_.show_type = SHOW_ASCII;
525 follow_info_.show_stream = BOTH_HOSTS;
528 follow_stats(&stats);
530 follow_info_.is_ipv6 = stats.is_ipv6;
537 const int FollowStreamDialog::max_document_length_ = 2 * 1000 * 1000; // Just a guess
538 void FollowStreamDialog::addText(QString text, gboolean is_from_server, guint32 packet_num)
540 if (save_as_ == true)
542 //FILE *fh = (FILE *)arg;
544 int FileDescriptor = file_.handle();
545 FILE* fh = fdopen(dup(FileDescriptor), "wb");
546 nwritten = fwrite(text.toUtf8().constData(), text.length(), 1, fh);
548 if ((int)nwritten != text.length()) {
550 report_an_error_maybe();
560 int char_count = ui->teStreamContent->document()->characterCount();
561 if (char_count + text.length() > max_document_length_) {
562 text.truncate(max_document_length_ - char_count);
566 QColor tagserver_fg = ColorUtils::fromColorT(prefs.st_server_fg);
567 QColor tagserver_bg = ColorUtils::fromColorT(prefs.st_server_bg);
569 QColor tagclient_fg = ColorUtils::fromColorT(prefs.st_client_fg);
570 QColor tagclient_bg = ColorUtils::fromColorT(prefs.st_client_bg);
572 ui->teStreamContent->moveCursor(QTextCursor::End);
573 ui->teStreamContent->setCurrentFont(wsApp->monospaceFont());
576 ui->teStreamContent->setTextColor(tagserver_fg);
577 ui->teStreamContent->setTextBackgroundColor(tagserver_bg);
581 ui->teStreamContent->setTextColor(tagclient_fg);
582 ui->teStreamContent->setTextBackgroundColor(tagclient_bg);
584 ui->teStreamContent->insertPlainText(text);
585 ui->teStreamContent->moveCursor(QTextCursor::End);
586 text_pos_to_packet_[ui->teStreamContent->textCursor().anchor()] = packet_num;
589 ui->teStreamContent->setTextBackgroundColor(ui->teStreamContent->palette().window().color());
590 ui->teStreamContent->setTextColor(ui->teStreamContent->palette().windowText().color());
591 ui->teStreamContent->insertPlainText(tr("\n[Stream output truncated]"));
592 ui->teStreamContent->moveCursor(QTextCursor::End);
596 void FollowStreamDialog::setCaptureFile(capture_file *cf)
598 if (!cf) { // We only want to know when the file closes.
604 // The following keyboard shortcuts should work (although
605 // they may not work consistently depending on focus):
606 // / (slash), Ctrl-F - Focus and highlight the search box
607 // Ctrl-G, Ctrl-N, F3 - Find next
608 // Should we make it so that typing any text starts searching?
609 bool FollowStreamDialog::eventFilter(QObject *obj, QEvent *event)
612 if (ui->teStreamContent->hasFocus() && event->type() == QEvent::KeyPress) {
613 QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
614 if (keyEvent->matches(QKeySequence::SelectAll) || keyEvent->matches(QKeySequence::Copy)
615 || keyEvent->text().isEmpty()) {
618 ui->leFind->setFocus();
619 if (keyEvent->matches(QKeySequence::Find)) {
621 } else if (keyEvent->matches(QKeySequence::FindNext)) {
630 void FollowStreamDialog::keyPressEvent(QKeyEvent *event)
632 if (ui->leFind->hasFocus()) {
633 if (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return) {
638 if (event->key() == Qt::Key_Slash || event->matches(QKeySequence::Find)) {
639 ui->leFind->setFocus();
640 ui->leFind->selectAll();
645 if (event->key() == Qt::Key_F3 || (event->key() == Qt::Key_N && event->modifiers() & Qt::ControlModifier)) {
650 QDialog::keyPressEvent(event);
653 static inline void sanitize_buffer(char *buffer, size_t nchars) {
654 for (size_t i = 0; i < nchars; i++) {
655 if (buffer[i] == '\n' || buffer[i] == '\r' || buffer[i] == '\t')
657 if (! g_ascii_isprint((guchar)buffer[i])) {
664 FollowStreamDialog::showBuffer(char *buffer, size_t nchars, gboolean is_from_server, guint32 packet_num,
669 static const gchar hexchars[16] = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
671 switch (follow_info_.show_type) {
675 /* If our native arch is ASCII, call: */
676 EBCDIC_to_ASCII((guint8*)buffer, (guint) nchars);
677 sanitize_buffer(buffer, nchars);
678 QByteArray ba = QByteArray(buffer, (int)nchars);
679 addText(ba, is_from_server, packet_num);
685 /* If our native arch is EBCDIC, call:
686 * ASCII_TO_EBCDIC(buffer, nchars);
688 sanitize_buffer(buffer, nchars);
689 sanitize_buffer(buffer, nchars);
690 QByteArray ba = QByteArray(buffer, (int)nchars);
691 addText(ba, is_from_server, packet_num);
695 case SHOW_RAW: // UTF-8
697 // The QString docs say that invalid characters will be replaced with
698 // replacement characters or removed. It would be nice if we could
699 // explicitly choose one or the other.
700 QString utf8 = QString::fromUtf8(buffer, (int)nchars);
701 addText(utf8, is_from_server, packet_num);
707 while (current_pos < nchars) {
710 gchar *cur = hexbuf, *ascii_start;
712 /* is_from_server indentation : put 4 spaces at the
713 * beginning of the string */
714 /* XXX - We might want to prepend each line with "C" or "S" instead. */
715 if (is_from_server && follow_info_.show_stream == BOTH_HOSTS) {
719 cur += g_snprintf(cur, 20, "%08X ", *global_pos);
720 /* 49 is space consumed by hex chars */
721 ascii_start = cur + 49;
722 for (i = 0; i < 16 && current_pos + i < nchars; i++) {
724 hexchars[(buffer[current_pos + i] & 0xf0) >> 4];
726 hexchars[buffer[current_pos + i] & 0x0f];
731 /* Fill it up if column isn't complete */
732 while (cur < ascii_start)
735 /* Now dump bytes as text */
736 for (i = 0; i < 16 && current_pos + i < nchars; i++) {
738 (g_ascii_isprint((guchar)buffer[current_pos + i]) ?
739 buffer[current_pos + i] : '.' );
749 addText(hexbuf, is_from_server, packet_num);
755 g_snprintf(initbuf, sizeof(initbuf), "char peer%d_%d[] = { /* Packet %u */\n",
756 is_from_server ? 1 : 0,
757 is_from_server ? server_buffer_count_++ : client_buffer_count_++,
759 addText(initbuf, is_from_server, packet_num);
761 while (current_pos < nchars) {
766 for (i = 0; i < 8 && current_pos + i < nchars; i++) {
767 /* Prepend entries with "0x" */
771 hexchars[(buffer[current_pos + i] & 0xf0) >> 4];
773 hexchars[buffer[current_pos + i] & 0x0f];
775 /* Delimit array entries with a comma */
776 if (current_pos + i + 1 < nchars)
782 /* Terminate the array if we are at the end */
783 if (current_pos + i == nchars) {
790 hexbuf[cur++] = '\n';
792 addText(hexbuf, is_from_server, packet_num);
800 const int base64_raw_len = 57; // Encodes to 76 bytes, common in RFCs
803 if (packet_num != last_packet_) {
804 yaml_text.append(QString("# Packet %1\npeer%2_%3: !!binary |\n")
806 .arg(is_from_server ? 1 : 0)
807 .arg(is_from_server ? server_buffer_count_++ : client_buffer_count_++));
809 while (current_pos < nchars) {
810 int len = current_pos + base64_raw_len < nchars ? base64_raw_len : (int) nchars - current_pos;
811 QByteArray base64_data(&buffer[current_pos], len);
813 yaml_text += " " + base64_data.toBase64() + "\n";
816 (*global_pos) += len;
818 addText(yaml_text, is_from_server, packet_num);
823 if (last_packet_ == 0) {
824 last_from_server_ = is_from_server;
827 if (packet_num != last_packet_) {
828 last_packet_ = packet_num;
829 if (is_from_server) {
830 server_packet_count_++;
832 client_packet_count_++;
834 if (last_from_server_ != is_from_server) {
835 last_from_server_ = is_from_server;
843 bool FollowStreamDialog::follow(QString previous_filter, bool use_tcp_index)
846 QString follow_filter;
847 const char *hostname0 = NULL, *hostname1 = NULL;
848 char *port0 = NULL, *port1 = NULL;
849 QString server_to_client_string;
850 QString client_to_server_string;
851 QString both_directions_string;
852 follow_stats_t stats;
856 gboolean is_tcp = FALSE, is_udp = FALSE;
860 if (cap_file_ == NULL)
862 QMessageBox::warning(this, tr("No capture file."), tr("Please make sure you have a capture file opened."));
866 if (cap_file_->edt == NULL)
868 QMessageBox::warning(this, tr("Error following stream."), tr("Capture file invalid."));
872 proto_get_frame_protocols(cap_file_->edt->pi.layers, NULL, &is_tcp, &is_udp, NULL, NULL);
874 switch (follow_type_)
878 QMessageBox::warning(this, tr("Error following stream."), tr("Please make sure you have a TCP packet selected."));
883 removeStreamControls();
885 QMessageBox::warning(this, tr("Error following stream."), tr("Please make sure you have a UDP packet selected."));
890 /* we got ssl so we can follow */
891 removeStreamControls();
892 if (!epan_dissect_packet_contains_field(cap_file_->edt, "ssl")) {
893 QMessageBox::critical(this, tr("Error following stream."),
894 tr("Please make sure you have an SSL packet selected."));
900 if (follow_type_ == FOLLOW_TCP || follow_type_ == FOLLOW_SSL)
902 /* Create a new filter that matches all packets in the TCP stream,
903 and set the display filter entry accordingly */
904 reset_tcp_reassembly();
908 follow_filter = gchar_free_to_qstring(build_follow_index_filter());
910 follow_filter = gchar_free_to_qstring(build_follow_conv_filter(&cap_file_->edt->pi));
912 if (follow_filter.isEmpty()) {
913 QMessageBox::warning(this,
914 tr("Error creating filter for this stream."),
915 tr("A transport or network layer header is needed."));
919 if (follow_type_ == FOLLOW_TCP || follow_type_ == FOLLOW_SSL)
921 /* Create a temporary file into which to dump the reassembled data
922 from the TCP stream, and set "data_out_file" to refer to it, so
923 that the TCP code will write to it.
925 XXX - it might be nicer to just have the TCP code directly
926 append stuff to the text widget for the TCP stream window,
927 if we can arrange that said window not pop up until we're
929 gchar *data_out_filename;
930 tmp_fd = create_tempfile(&data_out_filename, "follow");
931 data_out_filename_ = data_out_filename;
934 QMessageBox::warning(this, "Error",
935 "Could not create temporary file %1: %2",
936 data_out_filename_, g_strerror(errno));
937 data_out_filename_.clear();
941 data_out_file = fdopen(tmp_fd, "w+b");
942 if (data_out_file == NULL) {
943 QMessageBox::warning(this, "Error",
944 "Could not create temporary file %1: %2",
945 data_out_filename_, g_strerror(errno));
947 ws_unlink(data_out_filename_.toUtf8().constData());
948 data_out_filename_.clear();
953 /* append the negation */
954 if(!previous_filter.isEmpty()) {
955 filter_out_filter_ = QString("%1 and !(%2)")
956 .arg(previous_filter).arg(follow_filter);
960 filter_out_filter_ = QString("!(%1)").arg(follow_filter);
963 switch (follow_type_)
967 int stream_count = get_tcp_stream_count() - 1;
968 ui->streamNumberSpinBox->blockSignals(true);
969 ui->streamNumberSpinBox->setMaximum(stream_count);
970 ui->streamNumberSpinBox->setValue(get_follow_tcp_index());
971 ui->streamNumberSpinBox->blockSignals(false);
972 ui->streamNumberSpinBox->setToolTip(tr("%Ln total stream(s).", "", stream_count));
973 ui->streamNumberLabel->setToolTip(ui->streamNumberSpinBox->toolTip());
978 /* data will be passed via tap callback*/
979 msg = register_tap_listener("udp_follow", &follow_info_,
980 follow_filter.toUtf8().constData(),
981 0, NULL, udp_queue_packet_data, NULL);
983 QMessageBox::critical(this, "Error",
984 "Can't register udp_follow tap: %1",
990 /* we got ssl so we can follow */
991 msg = register_tap_listener("ssl", &follow_info_,
992 follow_filter.toUtf8().constData(), 0,
993 NULL, ssl_queue_packet_data, NULL);
996 QMessageBox::critical(this, "Error",
997 "Can't register ssl tap: %1", msg->str);
1003 /* Run the display filter so it goes in effect - even if it's the
1004 same as the previous display filter. */
1005 emit updateFilter(follow_filter, TRUE);
1007 switch (follow_type_)
1013 remove_tap_listener(&follow_info_);
1016 remove_tap_listener(&follow_info_);
1020 if (follow_type_ == FOLLOW_TCP)
1022 /* Check whether we got any data written to the file. */
1023 if (empty_tcp_stream) {
1024 QMessageBox::warning(this, "Error",
1025 "The packets in the capture file for that stream have no data.");
1027 ws_unlink(data_out_filename_.toUtf8().constData());
1028 data_out_filename_.clear();
1032 /* Go back to the top of the file and read the first tcp_stream_chunk
1033 * to ensure that the IP addresses and port numbers in the drop-down
1034 * list are tied to the correct lines displayed by follow_read_stream()
1035 * later on (which also reads from this file). Close the file when
1038 * We read the data now, before we pop up a window, in case the
1039 * read fails. We use the data later.
1042 rewind(data_out_file);
1043 nchars=fread(&sc, 1, sizeof(sc), data_out_file);
1044 if (nchars != sizeof(sc)) {
1045 if (ferror(data_out_file)) {
1046 QMessageBox::warning(this, "Error",
1047 QString(tr("Could not read from temporary file %1: %2"))
1048 .arg(data_out_filename_)
1049 .arg(g_strerror(errno)));
1051 QMessageBox::warning(this, "Error",
1052 QString(tr("Short read from temporary file %1: expected %2, got %3"))
1053 .arg(data_out_filename_)
1054 .arg((unsigned long)sizeof(sc))
1055 .arg((unsigned long)nchars));
1059 ws_unlink(data_out_filename_.toUtf8().constData());
1060 data_out_filename_.clear();
1063 fclose(data_out_file);
1064 data_out_file = NULL;
1067 /* Stream to show */
1068 follow_stats(&stats);
1070 if (stats.is_ipv6) {
1071 struct e_in6_addr ipaddr;
1072 memcpy(&ipaddr, stats.ip_address[0], 16);
1073 hostname0 = get_hostname6(&ipaddr);
1074 memcpy(&ipaddr, stats.ip_address[1], 16);
1075 hostname1 = get_hostname6(&ipaddr);
1078 memcpy(&ipaddr, stats.ip_address[0], 4);
1079 hostname0 = get_hostname(ipaddr);
1080 memcpy(&ipaddr, stats.ip_address[1], 4);
1081 hostname1 = get_hostname(ipaddr);
1084 switch (follow_type_)
1087 port0 = ep_tcp_port_to_display(stats.port[0]);
1088 port1 = ep_tcp_port_to_display(stats.port[1]);
1091 port0 = ep_udp_port_to_display(stats.port[0]);
1092 port1 = ep_udp_port_to_display(stats.port[1]);
1095 port0 = ep_tcp_port_to_display(stats.port[0]);
1096 port1 = ep_tcp_port_to_display(stats.port[1]);
1100 follow_info_.is_ipv6 = stats.is_ipv6;
1102 if (follow_type_ == FOLLOW_TCP)
1104 /* Host 0 --> Host 1 */
1105 if ((sc.src_port == stats.port[0]) &&
1106 ((stats.is_ipv6 && (memcmp(sc.src_addr, stats.ip_address[0], 16) == 0)) ||
1107 (!stats.is_ipv6 && (memcmp(sc.src_addr, stats.ip_address[0], 4) == 0)))) {
1108 server_to_client_string =
1109 QString("%1:%2 %3 %4:%5 (%6)")
1110 .arg(hostname0).arg(port0)
1111 .arg(UTF8_RIGHTWARDS_ARROW)
1112 .arg(hostname1).arg(port1)
1113 .arg(gchar_free_to_qstring(format_size(
1114 stats.bytes_written[0],
1115 format_size_unit_bytes|format_size_prefix_si)));
1117 server_to_client_string =
1118 QString("%1:%2 %3 %4:%5 (%6)")
1119 .arg(hostname1).arg(port1)
1120 .arg(UTF8_RIGHTWARDS_ARROW)
1121 .arg(hostname0).arg(port0)
1122 .arg(gchar_free_to_qstring(format_size(
1123 stats.bytes_written[0],
1124 format_size_unit_bytes|format_size_prefix_si)));
1127 /* Host 1 --> Host 0 */
1128 if ((sc.src_port == stats.port[1]) &&
1129 ((stats.is_ipv6 && (memcmp(sc.src_addr, stats.ip_address[1], 16) == 0)) ||
1130 (!stats.is_ipv6 && (memcmp(sc.src_addr, stats.ip_address[1], 4) == 0)))) {
1131 client_to_server_string =
1132 QString("%1:%2 %3 %4:%5 (%6)")
1133 .arg(hostname0).arg(port0)
1134 .arg(UTF8_RIGHTWARDS_ARROW)
1135 .arg(hostname1).arg(port1)
1136 .arg(gchar_free_to_qstring(format_size(
1137 stats.bytes_written[1],
1138 format_size_unit_bytes|format_size_prefix_si)));
1140 client_to_server_string =
1141 QString("%1:%2 %3 %4:%5 (%6)")
1142 .arg(hostname1).arg(port1)
1143 .arg(UTF8_RIGHTWARDS_ARROW)
1144 .arg(hostname0).arg(port0)
1145 .arg(gchar_free_to_qstring(format_size(
1146 stats.bytes_written[1],
1147 format_size_unit_bytes|format_size_prefix_si)));
1153 if ((follow_info_.client_port == stats.port[0]) &&
1154 ((stats.is_ipv6 && (memcmp(follow_info_.client_ip.data, stats.ip_address[0], 16) == 0)) ||
1155 (!stats.is_ipv6 && (memcmp(follow_info_.client_ip.data, stats.ip_address[0], 4) == 0)))) {
1156 server_to_client_string =
1157 QString("%1:%2 %3 %4:%5 (%6)")
1158 .arg(hostname0).arg(port0)
1159 .arg(UTF8_RIGHTWARDS_ARROW)
1160 .arg(hostname1).arg(port1)
1161 .arg(gchar_free_to_qstring(format_size(
1162 follow_info_.bytes_written[0],
1163 format_size_unit_bytes|format_size_prefix_si)));
1165 client_to_server_string =
1166 QString("%1:%2 %3 %4:%5 (%6)")
1167 .arg(hostname1).arg(port1)
1168 .arg(UTF8_RIGHTWARDS_ARROW)
1169 .arg(hostname0).arg(port0)
1170 .arg(gchar_free_to_qstring(format_size(
1171 follow_info_.bytes_written[1],
1172 format_size_unit_bytes|format_size_prefix_si)));
1174 server_to_client_string =
1175 QString("%1:%2 %3 %4:%5 (%6)")
1176 .arg(hostname1).arg(port1)
1177 .arg(UTF8_RIGHTWARDS_ARROW)
1178 .arg(hostname0).arg(port0)
1179 .arg(gchar_free_to_qstring(format_size(
1180 follow_info_.bytes_written[0],
1181 format_size_unit_bytes|format_size_prefix_si)));
1183 client_to_server_string =
1184 QString("%1:%2 %3 %4:%5 (%6)")
1185 .arg(hostname0).arg(port0)
1186 .arg(UTF8_RIGHTWARDS_ARROW)
1187 .arg(hostname1).arg(port1)
1188 .arg(gchar_free_to_qstring(format_size(
1189 follow_info_.bytes_written[1],
1190 format_size_unit_bytes|format_size_prefix_si)));
1194 /* Both Stream Directions */
1195 switch (follow_type_)
1198 both_directions_string = QString("Entire conversation (%1)")
1199 .arg(gchar_free_to_qstring(format_size(
1200 stats.bytes_written[0] + stats.bytes_written[1],
1201 format_size_unit_bytes|format_size_prefix_si)));
1202 this->setWindowTitle(QString("Follow TCP Stream (%1)").arg(follow_filter));
1205 both_directions_string = QString("Entire conversation (%1)")
1206 .arg(gchar_free_to_qstring(format_size(
1207 follow_info_.bytes_written[0] + follow_info_.bytes_written[1],
1208 format_size_unit_bytes|format_size_prefix_si)));
1209 this->setWindowTitle(QString("Follow UDP Stream (%1)").arg(follow_filter));
1212 both_directions_string = QString("Entire conversation (%1)")
1213 .arg(gchar_free_to_qstring(format_size(
1214 follow_info_.bytes_written[0] + follow_info_.bytes_written[1],
1215 format_size_unit_bytes|format_size_prefix_si)));
1216 this->setWindowTitle(QString("Follow SSL Stream (%1)").arg(follow_filter));
1220 ui->cbDirections->clear();
1221 this->ui->cbDirections->addItem(both_directions_string);
1222 this->ui->cbDirections->addItem(client_to_server_string);
1223 this->ui->cbDirections->addItem(server_to_client_string);
1228 if (data_out_file) {
1229 fclose(data_out_file);
1230 data_out_file = NULL;
1236 #define FLT_BUF_SIZE 1024
1239 * XXX - the routine pointed to by "print_line_fcn_p" doesn't get handed lines,
1240 * it gets handed bufferfuls. That's fine for "follow_write_raw()"
1241 * and "follow_add_to_gtk_text()", but, as "follow_print_text()" calls
1242 * the "print_line()" routine from "print.c", and as that routine might
1243 * genuinely expect to be handed a line (if, for example, it's using
1244 * some OS or desktop environment's printing API, and that API expects
1245 * to be handed lines), "follow_print_text()" should probably accumulate
1246 * lines in a buffer and hand them "print_line()". (If there's a
1247 * complete line in a buffer - i.e., there's nothing of the line in
1248 * the previous buffer or the next buffer - it can just hand that to
1249 * "print_line()" after filtering out non-printables, as an
1252 * This might or might not be the reason why C arrays display
1253 * correctly but get extra blank lines very other line when printed.
1256 FollowStreamDialog::readTcpStream()
1259 tcp_stream_chunk sc;
1263 guint8 client_addr[MAX_IPADDR_LEN];
1264 guint16 client_port = 0;
1266 guint32 global_client_pos = 0, global_server_pos = 0;
1267 guint32 *global_pos;
1269 char buffer[FLT_BUF_SIZE+1]; /* +1 to fix ws bug 1043 */
1271 frs_return_t frs_return;
1273 iplen = (follow_info_.is_ipv6) ? 16 : 4;
1275 data_out_fp = ws_fopen(data_out_filename_.toUtf8().constData(), "rb");
1276 if (data_out_fp == NULL) {
1277 QMessageBox::critical(this, "Error",
1278 "Could not open temporary file %1: %2", data_out_filename_,
1280 return FRS_OPEN_ERROR;
1283 while ((nchars=fread(&sc, 1, sizeof(sc), data_out_fp))) {
1284 if (nchars != sizeof(sc)) {
1285 QMessageBox::critical(this, "Error",
1286 QString(tr("Short read from temporary file %1: expected %2, got %3"))
1287 .arg(data_out_filename_)
1290 fclose(data_out_fp);
1292 return FRS_READ_ERROR;
1294 if (client_port == 0) {
1295 memcpy(client_addr, sc.src_addr, iplen);
1296 client_port = sc.src_port;
1299 if (memcmp(client_addr, sc.src_addr, iplen) == 0 &&
1300 client_port == sc.src_port) {
1302 global_pos = &global_client_pos;
1303 if (follow_info_.show_stream == FROM_SERVER) {
1308 global_pos = &global_server_pos;
1309 if (follow_info_.show_stream == FROM_CLIENT) {
1315 while (bytes_read < sc.dlen) {
1316 bcount = ((sc.dlen-bytes_read) < FLT_BUF_SIZE) ? (sc.dlen-bytes_read) : FLT_BUF_SIZE;
1317 nchars = fread(buffer, 1, bcount, data_out_fp);
1320 /* XXX - if we don't get "bcount" bytes, is that an error? */
1321 bytes_read += nchars;
1325 frs_return = showBuffer(buffer,
1326 nchars, is_server, sc.packet_num, global_pos);
1327 if(frs_return == FRS_PRINT_ERROR) {
1328 fclose(data_out_fp);
1337 if (ferror(data_out_fp)) {
1338 QMessageBox::critical(this, tr("Error reading temporary file"),
1339 QString("%1: %2").arg(data_out_filename_).arg(g_strerror(errno)));
1340 fclose(data_out_fp);
1342 return FRS_READ_ERROR;
1345 fclose(data_out_fp);
1351 * XXX - the routine pointed to by "print_line_fcn_p" doesn't get handed lines,
1352 * it gets handed bufferfuls. That's fine for "follow_write_raw()"
1353 * and "follow_add_to_gtk_text()", but, as "follow_print_text()" calls
1354 * the "print_line()" routine from "print.c", and as that routine might
1355 * genuinely expect to be handed a line (if, for example, it's using
1356 * some OS or desktop environment's printing API, and that API expects
1357 * to be handed lines), "follow_print_text()" should probably accumulate
1358 * lines in a buffer and hand them "print_line()". (If there's a
1359 * complete line in a buffer - i.e., there's nothing of the line in
1360 * the previous buffer or the next buffer - it can just hand that to
1361 * "print_line()" after filtering out non-printables, as an
1364 * This might or might not be the reason why C arrays display
1365 * correctly but get extra blank lines very other line when printed.
1368 FollowStreamDialog::readUdpStream()
1370 guint32 global_client_pos = 0, global_server_pos = 0;
1371 guint32 *global_pos;
1374 frs_return_t frs_return;
1375 follow_record_t *follow_record;
1378 for (cur = follow_info_.payload; cur; cur = g_list_next(cur)) {
1379 follow_record = (follow_record_t *)cur->data;
1381 if (!follow_record->is_server) {
1382 global_pos = &global_client_pos;
1383 if(follow_info_.show_stream == FROM_SERVER) {
1387 global_pos = &global_server_pos;
1388 if (follow_info_.show_stream == FROM_CLIENT) {
1394 buffer = (char *)g_memdup(follow_record->data->data,
1395 follow_record->data->len);
1397 frs_return = showBuffer(
1399 follow_record->data->len,
1400 follow_record->is_server,
1401 follow_record->packet_num,
1404 if(frs_return == FRS_PRINT_ERROR)
1418 * indent-tabs-mode: nil
1421 * ex: set shiftwidth=4 tabstop=8 expandtab:
1422 * :indentSize=4:tabSize=8:noTabs=true: