Qt: Better sorting for traffic dialogs

Allow the traffic columns to automatically sort on secondary columns if
applicable. e.g. the address and port column for TCP and UDP, or the
secondary address for conversations
This commit is contained in:
Roland Knall 2022-06-13 13:47:49 +02:00 committed by Roland Knall
parent b3a102eb46
commit 0cfe7a0d56
4 changed files with 101 additions and 33 deletions

View File

@ -33,6 +33,10 @@ wsbuglink:17779[]
- Adding/Removing tabs will keep them in the same order all the time
- If a filter is applied, two columns are shown in either dialog detailing the difference between
unmatched and matched packets
- Columns are now sorted via secondary properties if an identical entry is found.
- Conversations will be sorted via second address and first port number
- Endpoints will be sorted via port numbers
- IPv6 addresses are sorted correctly after IPv4 addresses
* The PCRE2 library (https://www.pcre.org/) is now a required dependency to build Wireshark.

View File

@ -107,7 +107,7 @@ bool ATapDataModel::enableTap()
/* The errorString is ignored. If this is not working, there is nothing really the user may do about
* it, so the error is only interesting to the developer.*/
GString * errorString = register_tap_listener(tap().toUtf8().constData(), hash(), _filter.toUtf8().constData(),
GString * errorString = register_tap_listener(tap().toUtf8().constData(), hash(), _filter.toUtf8().constData(),
TL_IGNORE_DISPLAY_FILTER, &ATapDataModel::tapReset, conversationPacketHandler(), &ATapDataModel::tapDraw, nullptr);
if (errorString && errorString->len > 0) {
_disableTap = true;
@ -497,6 +497,21 @@ QVariant EndpointDataModel::data(const QModelIndex &idx, int role) const
} else if (role == ATapDataModel::ROW_IS_FILTERED) {
return (bool)item->filtered && showTotalColumn();
}
else if (column == EndpointDataModel::ENDP_COLUMN_ADDR) {
if (role == ATapDataModel::DATA_ADDRESS_TYPE)
return (int)item->myaddress.type;
else if (role == ATapDataModel::DATA_IPV4_INTEGER && item->myaddress.type == AT_IPv4) {
const ws_in4_addr * ip4 = (const ws_in4_addr *) item->myaddress.data;
return (quint32) GUINT32_TO_BE(*ip4);
}
else if (role == ATapDataModel::DATA_IPV6_VECTOR && item->myaddress.type == AT_IPv6) {
const ws_in6_addr * ip6 = (const ws_in6_addr *) item->myaddress.data;
QVector<quint8> result;
result.reserve(16);
std::copy(ip6->bytes + 0, ip6->bytes + 16, std::back_inserter(result));
return QVariant::fromValue(result);
}
}
#ifdef HAVE_MAXMINDDB
else if (role == ATapDataModel::GEODATA_AVAILABLE) {
return (bool)(mmdb_lookup && maxmind_db_has_coords(mmdb_lookup));
@ -718,7 +733,7 @@ QVariant ConversationDataModel::data(const QModelIndex &idx, int role) const
QDateTime abs_dt = QDateTime::fromMSecsSinceEpoch(nstime_to_msec(abs_time));
return role == Qt::DisplayRole ? abs_dt.toString("hh:mm:ss.zzzz") : (QVariant)abs_dt;
} else {
return role == Qt::DisplayRole ?
return role == Qt::DisplayRole ?
QString::number(nstime_to_sec(&conv_item->start_time), 'f', width) :
(QVariant)((double) nstime_to_sec(&conv_item->start_time));
}
@ -737,7 +752,7 @@ QVariant ConversationDataModel::data(const QModelIndex &idx, int role) const
qlonglong packets = 0;
if (showTotalColumn())
packets = conv_item->tx_frames_total + conv_item->rx_frames_total;
return role == Qt::DisplayRole ? QString("%L1").arg(packets) : (QVariant)packets;
}
case 15:
@ -777,6 +792,22 @@ QVariant ConversationDataModel::data(const QModelIndex &idx, int role) const
} else if (role == ATapDataModel::ROW_IS_FILTERED) {
return (bool)conv_item->filtered && showTotalColumn();
}
else if (column == ConversationDataModel::CONV_COLUMN_SRC_ADDR || column == ConversationDataModel::CONV_COLUMN_DST_ADDR) {
address tst_address = column == ConversationDataModel::CONV_COLUMN_SRC_ADDR ? conv_item->src_address : conv_item->dst_address;
if (role == ATapDataModel::DATA_ADDRESS_TYPE)
return (int)tst_address.type;
else if (role == ATapDataModel::DATA_IPV4_INTEGER && tst_address.type == AT_IPv4) {
const ws_in4_addr * ip4 = (const ws_in4_addr *) tst_address.data;
return (quint32) GUINT32_TO_BE(*ip4);
}
else if (role == ATapDataModel::DATA_IPV6_VECTOR && tst_address.type == AT_IPv6) {
const ws_in6_addr * ip6 = (const ws_in6_addr *) tst_address.data;
QVector<quint8> result;
result.reserve(16);
std::copy(ip6->bytes + 0, ip6->bytes + 16, std::back_inserter(result));
return QVariant::fromValue(result);
}
}
return QVariant();
}

View File

@ -47,7 +47,10 @@ public:
TIMELINE_DATA,
ENDPOINT_DATATYPE,
CONVERSATION_ID,
ROW_IS_FILTERED
ROW_IS_FILTERED,
DATA_ADDRESS_TYPE,
DATA_IPV4_INTEGER,
DATA_IPV6_VECTOR,
};
typedef enum {

View File

@ -45,7 +45,6 @@
#include <QUrl>
#include <QTemporaryFile>
#include <QHBoxLayout>
#include <QRegularExpressionMatch>
TabData::TabData() :
_name(QString()),
@ -91,41 +90,72 @@ bool TrafficDataFilterProxy::lessThan(const QModelIndex &source_left, const QMod
if (! source_right.isValid() || ! qobject_cast<const ATapDataModel *>(source_right.model()))
return false;
ATapDataModel * model = qobject_cast<ATapDataModel *>(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);
QString strA = datA.toString().toLower();
QString strB = datB.toString().toLower();
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) {
return addressTypeA < addressTypeB;
} else if (addressTypeA == addressTypeB) {
bool result = false;
bool identical = false;
QRegularExpression re = QRegularExpression(QString("[a-f\\d]{2}:[a-f\\d]{2}:[a-f\\d]{2}:[a-f\\d]{2}:[a-f\\d]{2}:[a-f\\d]{2}"));
QRegularExpressionMatch match = re.match(strA);
if (match.hasMatch())
return strA < strB;
if (addressTypeA == AT_IPv4) {
quint32 valA = model->data(source_left, ATapDataModel::DATA_IPV4_INTEGER).value<quint32>();
quint32 valB = model->data(source_right, ATapDataModel::DATA_IPV4_INTEGER).value<quint32>();
QRegularExpression reIp("(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})");
match = reIp.match(strA);
QRegularExpressionMatch matchB = reIp.match(strB);
QStringList listA = strA.split('.');
QStringList listB = strB.split('.');
if (match.hasMatch() && listA.count() == 4 && ! matchB.hasMatch())
return true;
else if (match.hasMatch() && listA.count() == 4 && matchB.hasMatch() && listB.count() == 4 ) {
quint32 ipA = (listA.at(0).toInt() << 24) + (listA.at(1).toInt() << 16) + (listA.at(2).toInt() << 8) + listA.at(3).toInt();
quint32 ipB = (listB.at(0).toInt() << 24) + (listB.at(1).toInt() << 16) + (listB.at(2).toInt() << 8) + listB.at(3).toInt();
return ipA < ipB;
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<ConversationDataModel *>(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>();
quint32 valY = model->data(tstB, ATapDataModel::DATA_IPV4_INTEGER).value<quint32>();
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;
} else
return result;
}
QString iPv6Pattern("(([\\da-f]{1,4}:){7,7}[\\da-f]{1,4}|([\\da-f]{1,4}:){1,7}:|([\\da-f]{1,4}:){1,6}:[\\da-f]{1,4}|"
"([\\da-f]{1,4}:){1,5}(:[\\da-f]{1,4}){1,2}|([\\da-f]{1,4}:){1,4}(:[\\da-f]{1,4}){1,3}|([\\da-f]{1,4}:){1,3}(:"
"[\\da-f]{1,4}){1,4}|([\\da-f]{1,4}:){1,2}(:[\\da-f]{1,4}){1,5}|[\\da-f]{1,4}:((:[\\da-f]{1,4}){1,6})|:((:[\\da"
"-f]{1,4}){1,7}|:)|fe80:(:[\\da-f]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}"
"\\d){0,1}\\d)\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}\\d){0,1}\\d)|([\\da-f]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}\\d){0,1}"
"\\d)\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}\\d){0,1}\\d))");
QRegularExpression reIPv6(iPv6Pattern);
match = reIPv6.match(strA);
if (match.hasMatch())
return strA < strB;
if (datA.canConvert<double>() && datB.canConvert<double>())
return datA.toDouble() < datB.toDouble();