/** @file * * Wireshark - Network traffic analyzer * By Gerald Combs * Copyright 1998 Gerald Combs * * SPDX-License-Identifier: GPL-2.0-or-later */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include TabData::TabData() : _name(QString()), _protoId(-1) {} TabData::TabData(QString name, int protoId) : _name(name), _protoId(protoId) {} QString TabData::name() const { return _name; } int TabData::protoId() const { return _protoId; } TrafficDataFilterProxy::TrafficDataFilterProxy(QObject *parent) : QSortFilterProxyModel(parent) {} bool TrafficDataFilterProxy::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const { ATapDataModel * dataModel = qobject_cast(sourceModel()); if (dataModel) { bool isFiltered = dataModel->data(dataModel->index(source_row, 0), ATapDataModel::ROW_IS_FILTERED).toBool(); if (dataModel->filter().length() > 0) return ! isFiltered; } return QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent); } bool TrafficDataFilterProxy::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const { if (! source_left.isValid() || ! qobject_cast(source_left.model())) return false; if (! source_right.isValid() || ! qobject_cast(source_right.model())) return false; ATapDataModel * model = qobject_cast(sourceModel()); if (! model || source_left.model() != model || source_right.model() != model) return false; QVariant datA = source_left.data(ATapDataModel::UNFORMATTED_DISPLAYDATA); QVariant datB = source_right.data(ATapDataModel::UNFORMATTED_DISPLAYDATA); bool is_address = false; if (qobject_cast(model) && source_left.column() == EndpointDataModel::ENDP_COLUMN_ADDR && source_left.column() == source_right.column()) { is_address = true; } else if (qobject_cast(model) && (source_left.column() == ConversationDataModel::CONV_COLUMN_SRC_ADDR || source_left.column() == ConversationDataModel::CONV_COLUMN_DST_ADDR) && source_left.column() == source_right.column()) { is_address = true; } if (is_address) { bool result = false; bool identical = false; int addressTypeA = model->data(source_left, ATapDataModel::DATA_ADDRESS_TYPE).toInt(); int addressTypeB = model->data(source_right, ATapDataModel::DATA_ADDRESS_TYPE).toInt(); if (addressTypeA != 0 && addressTypeB != 0 && addressTypeA != addressTypeB) { result = addressTypeA < addressTypeB; } else if (addressTypeA != 0 && addressTypeA == addressTypeB) { if (addressTypeA == AT_IPv4) { quint32 valA = model->data(source_left, ATapDataModel::DATA_IPV4_INTEGER).value(); quint32 valB = model->data(source_right, ATapDataModel::DATA_IPV4_INTEGER).value(); result = valA < valB; identical = valA == valB; } else { result = QString::compare(datA.toString(), datB.toString(), Qt::CaseInsensitive) < 0; identical = QString::compare(datA.toString(), datB.toString(), Qt::CaseInsensitive) == 0; } int portColumn = EndpointDataModel::ENDP_COLUMN_PORT; if (identical && qobject_cast(model)) { QModelIndex tstA, tstB; if (source_left.column() == ConversationDataModel::CONV_COLUMN_SRC_ADDR) { portColumn = ConversationDataModel::CONV_COLUMN_SRC_PORT; int col = ConversationDataModel::CONV_COLUMN_DST_ADDR; if (model->portsAreHidden()) col -= 1; tstA = model->index(source_left.row(), col); tstB = model->index(source_right.row(), col); } else if (source_left.column() == ConversationDataModel::CONV_COLUMN_DST_ADDR) { portColumn = ConversationDataModel::CONV_COLUMN_DST_PORT; int col = ConversationDataModel::CONV_COLUMN_SRC_ADDR; if (model->portsAreHidden()) col -= 1; tstA = model->index(source_left.row(), col); tstB = model->index(source_right.row(), col); } if (addressTypeA == AT_IPv4) { quint32 valX = model->data(tstA, ATapDataModel::DATA_IPV4_INTEGER).value(); quint32 valY = model->data(tstB, ATapDataModel::DATA_IPV4_INTEGER).value(); result = valX < valY; identical = valX == valY; } else { result = QString::compare(model->data(tstA).toString().toLower(), model->data(tstB).toString(), Qt::CaseInsensitive) < 0; identical = QString::compare(model->data(tstA).toString().toLower(), model->data(tstB).toString(), Qt::CaseInsensitive) == 0; } } if (! result && identical && ! model->portsAreHidden()) { int portA = model->data(model->index(source_left.row(), portColumn)).toInt(); int portB = model->data(model->index(source_right.row(), portColumn)).toInt(); return portA < portB; } } return result; } if (datA.canConvert() && datB.canConvert()) return datA.toDouble() < datB.toDouble(); return QSortFilterProxyModel::lessThan(source_left, source_right); } TrafficTab::TrafficTab(QWidget * parent) : DetachableTabWidget(parent) { _createModel = nullptr; _disableTaps = false; _nameResolution = false; setTabBasename(QString()); } TrafficTab::~TrafficTab() {} void TrafficTab::setProtocolInfo(QString tableName, QList allProtocols, QList openTabs, ATapModelCallback createModel) { setTabBasename(tableName); _allProtocols = allProtocols; if (createModel) _createModel = createModel; setOpenTabs(openTabs); } void TrafficTab::setDelegate(int column, ATapCreateDelegate createDelegate) { if (! createDelegate || column < 0) return; if (_createDelegates.keys().contains(column)) _createDelegates.remove(column); _createDelegates.insert(column, createDelegate); for (int idx = 0; idx < count(); idx++) { int setColumn = column; ATapDataModel * model = modelForTabIndex(idx); if (model->portsAreHidden()) { if (model->modelType() == ATapDataModel::DATAMODEL_ENDPOINT && column > EndpointDataModel::ENDP_COLUMN_PORT) setColumn -= 1; else if (model->modelType() == ATapDataModel::DATAMODEL_CONVERSATION && column > ConversationDataModel::CONV_COLUMN_DST_PORT) setColumn -= 2; } if (qobject_cast(widget(idx))) { QTreeView * tree = qobject_cast(widget(idx)); tree->setItemDelegateForColumn(setColumn, createDelegate(tree)); } } } QTreeView * TrafficTab::createTree(int protoId) { TrafficTree * tree = new TrafficTree(tabBasename(), this); if (_createModel) { ATapDataModel * model = _createModel(protoId, ""); connect(model, &ATapDataModel::tapListenerChanged, tree, &TrafficTree::tapListenerEnabled); model->enableTap(); foreach(int col, _createDelegates.keys()) { if (_createDelegates[col]) { ATapCreateDelegate creator = _createDelegates[col]; tree->setItemDelegateForColumn(col, creator(tree)); } } TrafficDataFilterProxy * proxyModel = new TrafficDataFilterProxy(); proxyModel->setSourceModel(model); tree->setModel(proxyModel); QItemSelectionModel * ism = new QItemSelectionModel(proxyModel, tree); tree->setSelectionModel(ism); connect(ism, &QItemSelectionModel::currentChanged, this, &TrafficTab::doCurrentIndexChange); tree->sortByColumn(0, Qt::AscendingOrder); connect(proxyModel, &TrafficDataFilterProxy::modelReset, this, [tree]() { if (tree->model()->rowCount() > 0) { for (int col = 0; col < tree->model()->columnCount(); col++) tree->resizeColumnToContents(col); } }); connect(proxyModel, &TrafficDataFilterProxy::modelReset, this, &TrafficTab::modelReset); } return tree; } void TrafficTab::useAbsoluteTime(bool absolute) { for(int idx = 0; idx < count(); idx++) { ATapDataModel * atdm = modelForTabIndex(idx); if (atdm) atdm->useAbsoluteTime(absolute); } } void TrafficTab::useNanosecondTimestamps(bool nanoseconds) { for(int idx = 0; idx < count(); idx++) { ATapDataModel * atdm = modelForTabIndex(idx); if (atdm) atdm->useNanosecondTimestamps(nanoseconds); } } void TrafficTab::disableTap() { for(int idx = 0; idx < count(); idx++) { ATapDataModel * atdm = modelForTabIndex(idx); if (atdm) atdm->disableTap(); } _disableTaps = true; cornerWidget()->setEnabled(false); emit disablingTaps(); } void TrafficTab::setOpenTabs(QList protocols) { QList tabs = _tabs.keys(); QList remove; blockSignals(true); foreach(int protocol, protocols) { if (! tabs.contains(protocol)) { insertProtoTab(protocol, false); } tabs.removeAll(protocol); } foreach(int protocol, tabs) removeProtoTab(protocol, false); blockSignals(false); emit tabsChanged(_tabs.keys()); emit retapRequired(); } void TrafficTab::insertProtoTab(int protoId, bool emitSignals) { QList lUsed = _tabs.keys(); if (lUsed.contains(protoId) && lUsed.count() != count()) { _tabs.clear(); for (int idx = 0; idx < count(); idx++) { TabData tabData = qvariant_cast(tabBar()->tabData(idx)); _tabs.insert(tabData.protoId(), idx); } lUsed = _tabs.keys(); } if (protoId <= 0 || lUsed.contains(protoId)) return; QList lFull = _allProtocols; int idx = lFull.indexOf(protoId); if (idx < 0) return; QList part = lFull.mid(0, idx); int insertAt = 0; if (part.count() > 0) { for (int cnt = idx - 1; cnt >= 0; cnt--) { if (lUsed.contains(part[cnt]) && part[cnt] != protoId) { insertAt = lUsed.indexOf(part[cnt]) + 1; break; } } } QTreeView * tree = createTree(protoId); QString tableName = proto_get_protocol_short_name(find_protocol_by_id(protoId)); TabData tabData(tableName, protoId); QVariant storage; storage.setValue(tabData); if (tree->model()->rowCount() > 0) tableName += QString(" %1 %2").arg(UTF8_MIDDLE_DOT).arg(tree->model()->rowCount()); int tabId = -1; if (insertAt > -1) tabId = insertTab(insertAt, tree, tableName); else tabId = addTab(tree, tableName); if (tabId >= 0) tabBar()->setTabData(tabId, storage); /* We reset the correct tab idxs. That operations is costly, but it is only * called during this operation and ensures, that other operations do not * need to iterate, but rather can lookup the indeces. */ _tabs.clear(); for (int idx = 0; idx < count(); idx++) { TabData tabData = qvariant_cast(tabBar()->tabData(idx)); _tabs.insert(tabData.protoId(), idx); } if (emitSignals) { emit tabsChanged(_tabs.keys()); emit retapRequired(); } } void TrafficTab::removeProtoTab(int protoId, bool emitSignals) { if (_tabs.keys().contains(protoId)) { for(int idx = 0; idx < count(); idx++) { TabData tabData = qvariant_cast(tabBar()->tabData(idx)); if (protoId == tabData.protoId()) { removeTab(idx); break; } } } /* We reset the correct tab idxs. That operations is costly, but it is only * called during this operation and ensures, that other operations do not * need to iterate, but rather can lookup the indeces. */ _tabs.clear(); for (int idx = 0; idx < count(); idx++) { TabData tabData = qvariant_cast(tabBar()->tabData(idx)); _tabs.insert(tabData.protoId(), idx); } if (emitSignals) { emit tabsChanged(_tabs.keys()); emit retapRequired(); } } void TrafficTab::doCurrentIndexChange(const QModelIndex & cur, const QModelIndex &) { if (! cur.isValid()) return; const TrafficDataFilterProxy * proxy = qobject_cast(cur.model()); if (! proxy) return; ATapDataModel * model = qobject_cast(proxy->sourceModel()); if (! model) return; int tabId = _tabs[model->protoId()]; emit tabDataChanged(tabId); } QVariant TrafficTab::currentItemData(int role) { QTreeView * tree = qobject_cast(currentWidget()); if (tree) { QModelIndex idx = tree->selectionModel()->currentIndex(); /* In case no selection has been made yet, we select the topmostleft index, * to ensure proper handling. Especially ConversationDialog depends on this * method always returning data */ if (!idx.isValid()) { ATapDataModel * model = modelForTabIndex(currentIndex()); idx = model->index(0, 0); } return idx.data(role); } return QVariant(); } void TrafficTab::modelReset() { if (! qobject_cast(sender())) return; TrafficDataFilterProxy * qsfpm = qobject_cast(sender()); if (!qsfpm || ! qobject_cast(qsfpm->sourceModel())) return; ATapDataModel * atdm = qobject_cast(qsfpm->sourceModel()); int protoId = atdm->protoId(); if (!_tabs.keys().contains(protoId)) return; int tabIdx = _tabs[protoId]; TabData tabData = qvariant_cast(tabBar()->tabData(tabIdx)); if (tabData.protoId() == protoId) { if (qsfpm->rowCount() == 0) setTabText(tabIdx, tabData.name()); else setTabText(tabIdx, tabData.name() + QString(" %1 %2").arg(UTF8_MIDDLE_DOT).arg(qsfpm->rowCount())); } emit tabDataChanged(tabIdx); } ATapDataModel * TrafficTab::modelForTabIndex(int tabIdx) { if (tabIdx == -1) tabIdx = currentIndex(); return modelForWidget(widget(tabIdx)); } ATapDataModel * TrafficTab::modelForWidget(QWidget * searchWidget) { if (qobject_cast(searchWidget)) { QTreeView * tree = qobject_cast(searchWidget); if (qobject_cast(tree->model())) { TrafficDataFilterProxy * qsfpm = qobject_cast(tree->model()); if (qsfpm && qobject_cast(qsfpm->sourceModel())) { return qobject_cast(qsfpm->sourceModel()); } } } return nullptr; } void TrafficTab::setFilter(QString filter) { for (int idx = 0; idx < count(); idx++ ) { ATapDataModel * atdm = modelForTabIndex(idx); if (! atdm) continue; atdm->setFilter(filter); } } void TrafficTab::setNameResolution(bool checked) { if (checked == _nameResolution) return; for (int idx = 0; idx < count(); idx++ ) { ATapDataModel * atdm = modelForTabIndex(idx); if (! atdm) continue; atdm->setResolveNames(checked); } _nameResolution = checked; /* Send the signal, that all tabs have potentially changed */ emit tabDataChanged(-1); } bool TrafficTab::hasNameResolution(int tabIdx) { int tab = tabIdx == -1 || tabIdx >= count() ? currentIndex() : tabIdx; ATapDataModel * dataModel = modelForTabIndex(tab); if (! dataModel) return false; return dataModel->allowsNameResolution(); } QMenu * TrafficTab::createCopyMenu(QWidget *parent) { TrafficTree * tree = qobject_cast(currentWidget()); if ( ! tree) return nullptr; return tree->createCopyMenu(parent); } #ifdef HAVE_MAXMINDDB bool TrafficTab::hasGeoIPData(int tabIdx) { int tab = tabIdx == -1 || tabIdx >= count() ? currentIndex() : tabIdx; ATapDataModel * dataModel = modelForTabIndex(tab); return dataModel->hasGeoIPData(); } bool TrafficTab::writeGeoIPMapFile(QFile * fp, bool json_only, ATapDataModel * dataModel) { QTextStream out(fp); if (!json_only) { QFile ipmap(get_datafile_path("ipmap.html")); if (!ipmap.open(QIODevice::ReadOnly)) { QMessageBox::warning(this, tr("Map file error"), tr("Could not open base file %1 for reading: %2") .arg(get_datafile_path("ipmap.html")) .arg(g_strerror(errno)) ); return false; } /* Copy ipmap.html to map file. */ QTextStream in(&ipmap); QString line; while (in.readLineInto(&line)) { #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) out << line << Qt::endl; #else out << line << endl; #endif } out << QString("\n"); out.flush(); return true; } QUrl TrafficTab::createGeoIPMap(bool json_only, int tabIdx) { int tab = tabIdx == -1 || tabIdx >= count() ? currentIndex() : tabIdx; ATapDataModel * dataModel = modelForTabIndex(tab); if (! (dataModel && dataModel->hasGeoIPData())) { QMessageBox::warning(this, tr("Map file error"), tr("No endpoints available to map")); return QUrl(); } QString tempname = QString("%1/ipmapXXXXXX.html").arg(QDir::tempPath()); QTemporaryFile tf(tempname); if (!tf.open()) { QMessageBox::warning(this, tr("Map file error"), tr("Unable to create temporary file")); return QUrl(); } if (!writeGeoIPMapFile(&tf, json_only, dataModel)) { tf.close(); return QUrl(); } tf.setAutoRemove(false); return QUrl::fromLocalFile(tf.fileName()); } #endif void TrafficTab::detachTab(int tabIdx, QPoint pos) { ATapDataModel * model = modelForTabIndex(tabIdx); if (!model) return; TrafficTree * tree = qobject_cast(widget(tabIdx)); if (!tree) return; connect(this, &TrafficTab::disablingTaps ,tree , &TrafficTree::disableTap); DetachableTabWidget::detachTab(tabIdx, pos); removeProtoTab(model->protoId()); } void TrafficTab::attachTab(QWidget * content, QString name) { ATapDataModel * model = modelForWidget(content); if (!model) { attachTab(content, name); return; } insertProtoTab(model->protoId()); }