Qt: Cleanup Traffic Table

Remote traffic_table_ui.? and move the JSON stuff into endpoint.

This is in preparation for larger work on both the conversation
table as well as the endpoint table, and to start using Qt code
in the UI where it should be used.
This commit is contained in:
Roland Knall 2022-05-18 19:50:14 +00:00
parent 7079d881fa
commit f5b5d2c3c9
6 changed files with 218 additions and 358 deletions

View File

@ -52,7 +52,6 @@ set(NONGENERATED_UI_SRC
text_import.c
text_import_regex.c
time_shift.c
traffic_table_ui.c
util.c
voip_calls.c
)

View File

@ -15,7 +15,6 @@
#include "ui/recent.h"
#include "ui/tap-tcp-stream.h"
#include "ui/traffic_table_ui.h"
#include "wsutil/str_util.h"
@ -52,6 +51,46 @@
// - The value of 'Rel start' and 'Duration' in "Conversations" no need too precise https://gitlab.com/wireshark/wireshark/-/issues/12803
typedef enum {
CONV_COLUMN_SRC_ADDR,
CONV_COLUMN_SRC_PORT,
CONV_COLUMN_DST_ADDR,
CONV_COLUMN_DST_PORT,
CONV_COLUMN_PACKETS,
CONV_COLUMN_BYTES,
CONV_COLUMN_PKT_AB,
CONV_COLUMN_BYTES_AB,
CONV_COLUMN_PKT_BA,
CONV_COLUMN_BYTES_BA,
CONV_COLUMN_START,
CONV_COLUMN_DURATION,
CONV_COLUMN_BPS_AB,
CONV_COLUMN_BPS_BA,
CONV_NUM_COLUMNS,
CONV_INDEX_COLUMN = CONV_NUM_COLUMNS
} conversation_column_type_e;
static char const *conv_column_titles[CONV_NUM_COLUMNS] = {
"Address A",
"Port A",
"Address B",
"Port B",
"Packets",
"Bytes",
"Packets A " UTF8_RIGHTWARDS_ARROW " B",
"Bytes A " UTF8_RIGHTWARDS_ARROW " B",
"Packets B " UTF8_RIGHTWARDS_ARROW " A",
"Bytes B " UTF8_RIGHTWARDS_ARROW " A",
"Rel Start",
"Duration",
"Bits/s A " UTF8_RIGHTWARDS_ARROW " B",
"Bits/s B " UTF8_RIGHTWARDS_ARROW " A"
};
static char const *conv_conn_a_title = "Connection A";
static char const *conv_conn_b_title = "Connection B";
static char const *conv_abs_start_title = "Abs Start";
static const QString table_name_ = QObject::tr("Conversation");
ConversationDialog::ConversationDialog(QWidget &parent, CaptureFile &cf, int cli_proto_id, const char *filter) :
TrafficTableDialog(parent, cf, filter, table_name_),

View File

@ -15,8 +15,8 @@
#include <epan/to_str.h>
#include "ui/recent.h"
#include "ui/traffic_table_ui.h"
#include "wsutil/filesystem.h"
#include "wsutil/file_util.h"
#include "wsutil/pint.h"
#include "wsutil/str_util.h"
@ -35,6 +35,47 @@
#include <QUrl>
#include <QTemporaryFile>
#include <QJsonDocument>
#include <QJsonArray>
#include <QJsonObject>
#include <QJsonValue>
#include <QTextStream>
typedef enum
{
ENDP_COLUMN_ADDR,
ENDP_COLUMN_PORT,
ENDP_COLUMN_PACKETS,
ENDP_COLUMN_BYTES,
ENDP_COLUMN_PKT_AB,
ENDP_COLUMN_BYTES_AB,
ENDP_COLUMN_PKT_BA,
ENDP_COLUMN_BYTES_BA,
ENDP_NUM_COLUMNS,
ENDP_COLUMN_GEO_COUNTRY = ENDP_NUM_COLUMNS,
ENDP_COLUMN_GEO_CITY,
ENDP_COLUMN_GEO_AS_NUM,
ENDP_COLUMN_GEO_AS_ORG,
ENDP_NUM_GEO_COLUMNS
} endpoint_column_type_e;
static char const *endp_column_titles[ENDP_NUM_GEO_COLUMNS] = {
"Address",
"Port",
"Packets",
"Bytes",
"Tx Packets",
"Tx Bytes",
"Rx Packets",
"Rx Bytes",
"Country",
"City",
"AS Number",
"AS Organization"
};
static char const *endp_conn_title = "Connection";
static const QString table_name_ = QObject::tr("Endpoint");
EndpointDialog::EndpointDialog(QWidget &parent, CaptureFile &cf, int cli_proto_id, const char *filter) :
TrafficTableDialog(parent, cf, filter, table_name_)
@ -180,6 +221,137 @@ void EndpointDialog::tabChanged()
map_bt_->setEnabled(cur_tree && cur_tree->hasGeoIPData());
}
bool
EndpointDialog::writeEndpointGeoipMap(QFile * fp, bool json_only, hostlist_talker_t *const *hosts)
{
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("<script id=\"ipmap-data\" type=\"application/json\">\n");
}
/*
* Writes a feature for each resolved address, the output will look like:
* {
* "type": "FeatureCollection",
* "features": [
* {
* "type": "Feature",
* "geometry": {
* "type": "Point",
* "coordinates": [ -97.821999, 37.750999 ]
* },
* "properties": {
* "ip": "8.8.4.4",
* "autonomous_system_number": 15169,
* "autonomous_system_organization": "Google LLC",
* "city": "(omitted, but key is shown for documentation reasons)",
* "country": "United States",
* "radius": 1000,
* "packets": 1,
* "bytes": 1543
* }
* }
* ]
* }
*/
QJsonObject root;
root["type"] = "FeatureCollection";
QJsonArray features;
/* Append map data. */
size_t count = 0;
const hostlist_talker_t *host;
for (hostlist_talker_t *const *iter = hosts; (host = *iter) != NULL; ++iter) {
QJsonObject arrEntry;
char addr[WS_INET6_ADDRSTRLEN];
const mmdb_lookup_t *result = NULL;
if (host->myaddress.type == AT_IPv4) {
const ws_in4_addr *ip4 = (const ws_in4_addr *)host->myaddress.data;
result = maxmind_db_lookup_ipv4(ip4);
ws_inet_ntop4(ip4, addr, sizeof(addr));
} else if (host->myaddress.type == AT_IPv6) {
const ws_in6_addr *ip6 = (const ws_in6_addr *)host->myaddress.data;
result = maxmind_db_lookup_ipv6(ip6);
ws_inet_ntop6(ip6, addr, sizeof(addr));
}
if (!maxmind_db_has_coords(result)) {
// result could be NULL if the caller did not trigger a lookup
// before. result->found could be FALSE if no MMDB entry exists.
continue;
}
++count;
arrEntry["type"] = "Feature";
QJsonObject geometry;
geometry["type"] = "Point";
QJsonArray coordinates;
coordinates.append(QJsonValue(result->longitude));
coordinates.append(QJsonValue(result->latitude));
geometry["coordinates"] = coordinates;
arrEntry["geometry"] = geometry;
QJsonObject property;
property["ip"] = addr;
if (result->as_number && result->as_org) {
property["autonomous_system_number"] = QJsonValue((int)(result->as_number));
property["autonomous_system_organization"] = QJsonValue(result->as_org);
}
if (result->city)
property["city"] = result->city;
if (result->country)
property["country"] = result->country;
if (result->accuracy)
property["radius"] = QJsonValue(result->accuracy);
property["packets"] = QJsonValue((qint64)(host->rx_frames + host->tx_frames));
property["bytes"] = QJsonValue((qint64)(host->rx_bytes + host->tx_bytes));
arrEntry["properties"] = property;
features.append(arrEntry);
}
root["features"] = features;
QJsonDocument doc;
doc.setObject(root);
out << doc.toJson();
if (!json_only)
out << QString("</script>\n");
if (count == 0) {
QMessageBox::warning(this, tr("Map file error"), tr("No endpoints available to map"));
return false;
}
out.flush();
return true;
}
QUrl EndpointDialog::createMap(bool json_only)
{
EndpointTreeWidget *cur_tree = qobject_cast<EndpointTreeWidget *>(trafficTableTabWidget()->currentWidget());
@ -214,49 +386,12 @@ QUrl EndpointDialog::createMap(bool json_only)
return QUrl();
}
//
// XXX - At least with Qt 5.12 retrieving the name only works when
// it has been retrieved at least once when the file is open.
//
QString tempfilename = tf.fileName();
int fd = tf.handle();
//
// XXX - QFileDevice.handle() can return -1, but can QTemporaryFile.handle()
// do so if QTemporaryFile.open() has succeeded?
//
if (fd == -1) {
QMessageBox::warning(this, tr("Map file error"), tr("Unable to create temporary file"));
if (!writeEndpointGeoipMap(&tf, json_only, hosts)) {
g_free(hosts);
return QUrl();
}
// duplicate file descriptor as it is not allowed to perform a fclose before closing QFile
int duped_fd = ws_dup(fd);
if (duped_fd == -1) {
QMessageBox::warning(this, tr("Map file error"), tr("Unable to create temporary file"));
g_free(hosts);
return QUrl();
}
FILE* fp = ws_fdopen(duped_fd, "wb");
if (fp == NULL) {
QMessageBox::warning(this, tr("Map file error"), tr("Unable to create temporary file"));
g_free(hosts);
ws_close(duped_fd);
return QUrl();
}
gchar *err_str;
if (!write_endpoint_geoip_map(fp, json_only, hosts, &err_str)) {
QMessageBox::warning(this, tr("Map file error"), err_str);
g_free(err_str);
g_free(hosts);
fclose(fp);
tf.close();
return QUrl();
}
g_free(hosts);
if (fclose(fp) == EOF) {
QMessageBox::warning(this, tr("Map file error"), g_strerror(errno));
return QUrl();
}
tf.setAutoRemove(false);
return QUrl::fromLocalFile(tf.fileName());

View File

@ -10,6 +10,8 @@
#ifndef ENDPOINT_DIALOG_H
#define ENDPOINT_DIALOG_H
#include <QFile>
#include "traffic_table_dialog.h"
class EndpointTreeWidget : public TrafficTableTreeWidget
@ -68,6 +70,7 @@ private:
QPushButton *map_bt_;
QUrl createMap(bool json_only);
bool writeEndpointGeoipMap(QFile * fp, bool json_only, hostlist_talker_t *const *hosts);
#endif
bool addTrafficTable(register_ct_t* table);

View File

@ -1,220 +0,0 @@
/* traffic_table_ui.c
* Helper routines common to conversation/endpoint tables.
*
* Wireshark - Network traffic analyzer
* By Gerald Combs <gerald@wireshark.org>
* Copyright 1998 Gerald Combs
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "config.h"
#include <glib.h>
#include "traffic_table_ui.h"
#include <wsutil/utf8_entities.h>
#ifdef HAVE_MAXMINDDB
#include <errno.h>
#include "wsutil/filesystem.h"
#include "wsutil/file_util.h"
#include "wsutil/json_dumper.h"
#endif
const char *conv_column_titles[CONV_NUM_COLUMNS] = {
"Address A",
"Port A",
"Address B",
"Port B",
"Packets",
"Bytes",
"Packets A " UTF8_RIGHTWARDS_ARROW " B",
"Bytes A " UTF8_RIGHTWARDS_ARROW " B",
"Packets B " UTF8_RIGHTWARDS_ARROW " A",
"Bytes B " UTF8_RIGHTWARDS_ARROW " A",
"Rel Start",
"Duration",
"Bits/s A " UTF8_RIGHTWARDS_ARROW " B",
"Bits/s B " UTF8_RIGHTWARDS_ARROW " A"
};
const char *conv_conn_a_title = "Connection A";
const char *conv_conn_b_title = "Connection B";
const char *conv_abs_start_title = "Abs Start";
const char *endp_column_titles[ENDP_NUM_GEO_COLUMNS] = {
"Address",
"Port",
"Packets",
"Bytes",
"Tx Packets",
"Tx Bytes",
"Rx Packets",
"Rx Bytes",
"Country",
"City",
"AS Number",
"AS Organization"
};
const char *endp_conn_title = "Connection";
#ifdef HAVE_MAXMINDDB
gboolean
write_endpoint_geoip_map(FILE *fp, gboolean json_only, hostlist_talker_t *const *hosts, gchar **err_str)
{
if (!json_only) {
char *base_html_path = get_datafile_path("ipmap.html");
FILE *base_html_fp = ws_fopen(base_html_path, "rb");
if (!base_html_fp) {
*err_str = ws_strdup_printf("Could not open base file %s for reading: %s",
base_html_path, g_strerror(errno));
g_free(base_html_path);
return FALSE;
}
g_free(base_html_path);
/* Copy ipmap.html to map file. */
size_t n;
char buf[4096];
while ((n = fread(buf, 1, sizeof(buf), base_html_fp)) != 0) {
if (fwrite(buf, 1, n, fp) != n) {
*err_str = ws_strdup_printf("Failed to write to map file: %s", g_strerror(errno));
fclose(base_html_fp);
return FALSE;
}
}
if (ferror(base_html_fp)) {
*err_str = ws_strdup_printf("Failed to read base file: %s", g_strerror(errno));
fclose(base_html_fp);
return FALSE;
}
fclose(base_html_fp);
fputs("<script id=\"ipmap-data\" type=\"application/json\">\n", fp);
}
/*
* Writes a feature for each resolved address, the output will look like:
* {
* "type": "FeatureCollection",
* "features": [
* {
* "type": "Feature",
* "geometry": {
* "type": "Point",
* "coordinates": [ -97.821999, 37.750999 ]
* },
* "properties": {
* "ip": "8.8.4.4",
* "autonomous_system_number": 15169,
* "autonomous_system_organization": "Google LLC",
* "city": "(omitted, but key is shown for documentation reasons)",
* "country": "United States",
* "radius": 1000,
* "packets": 1,
* "bytes": 1543
* }
* }
* ]
* }
*/
json_dumper dumper = {
.output_file = fp,
.flags = JSON_DUMPER_FLAGS_PRETTY_PRINT
};
json_dumper_begin_object(&dumper);
json_dumper_set_member_name(&dumper, "type");
json_dumper_value_string(&dumper, "FeatureCollection");
json_dumper_set_member_name(&dumper, "features");
json_dumper_begin_array(&dumper);
/* Append map data. */
size_t count = 0;
const hostlist_talker_t *host;
for (hostlist_talker_t *const *iter = hosts; (host = *iter) != NULL; ++iter) {
char addr[WS_INET6_ADDRSTRLEN];
const mmdb_lookup_t *result = NULL;
if (host->myaddress.type == AT_IPv4) {
const ws_in4_addr *ip4 = (const ws_in4_addr *)host->myaddress.data;
result = maxmind_db_lookup_ipv4(ip4);
ws_inet_ntop4(ip4, addr, sizeof(addr));
} else if (host->myaddress.type == AT_IPv6) {
const ws_in6_addr *ip6 = (const ws_in6_addr *)host->myaddress.data;
result = maxmind_db_lookup_ipv6(ip6);
ws_inet_ntop6(ip6, addr, sizeof(addr));
}
if (!maxmind_db_has_coords(result)) {
// result could be NULL if the caller did not trigger a lookup
// before. result->found could be FALSE if no MMDB entry exists.
continue;
}
++count;
json_dumper_begin_object(&dumper);
json_dumper_set_member_name(&dumper, "type");
json_dumper_value_string(&dumper, "Feature");
json_dumper_set_member_name(&dumper, "geometry");
{
json_dumper_begin_object(&dumper);
json_dumper_set_member_name(&dumper, "type");
json_dumper_value_string(&dumper, "Point");
json_dumper_set_member_name(&dumper, "coordinates");
json_dumper_begin_array(&dumper);
json_dumper_value_double(&dumper, result->longitude);
json_dumper_value_double(&dumper, result->latitude);
json_dumper_end_array(&dumper); // end coordinates
}
json_dumper_end_object(&dumper); // end geometry
json_dumper_set_member_name(&dumper, "properties");
json_dumper_begin_object(&dumper);
{
json_dumper_set_member_name(&dumper, "ip");
json_dumper_value_string(&dumper, addr);
if (result->as_number && result->as_org) {
json_dumper_set_member_name(&dumper, "autonomous_system_number");
json_dumper_value_anyf(&dumper, "%u", result->as_number);
json_dumper_set_member_name(&dumper, "autonomous_system_organization");
json_dumper_value_string(&dumper, result->as_org);
}
if (result->city) {
json_dumper_set_member_name(&dumper, "city");
json_dumper_value_string(&dumper, result->city);
}
if (result->country) {
json_dumper_set_member_name(&dumper, "country");
json_dumper_value_string(&dumper, result->country);
}
if (result->accuracy) {
json_dumper_set_member_name(&dumper, "radius");
json_dumper_value_anyf(&dumper, "%u", result->accuracy);
}
json_dumper_set_member_name(&dumper, "packets");
json_dumper_value_anyf(&dumper, "%" PRIu64, host->rx_frames + host->tx_frames);
json_dumper_set_member_name(&dumper, "bytes");
json_dumper_value_anyf(&dumper, "%" PRIu64, host->rx_bytes + host->tx_bytes);
}
json_dumper_end_object(&dumper); // end properties
json_dumper_end_object(&dumper);
}
json_dumper_end_array(&dumper); // end features
json_dumper_end_object(&dumper);
json_dumper_finish(&dumper);
if (!json_only) {
fputs("</script>\n", fp);
}
if (count == 0) {
*err_str = g_strdup("No endpoints available to map");
return FALSE;
}
return TRUE;
}
#endif

View File

@ -1,96 +0,0 @@
/** @file
*
* Helper routines common to conversation/endpoint tables.
*
* Wireshark - Network traffic analyzer
* By Gerald Combs <gerald@wireshark.org>
* Copyright 1998 Gerald Combs
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef __TRAFFIC_TABLE_UI_H__
#define __TRAFFIC_TABLE_UI_H__
#ifdef HAVE_MAXMINDDB
#include <stdio.h>
#include "epan/maxmind_db.h"
#include <epan/conversation_table.h>
#endif
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
/** @file
* Conversation and endpoint lists.
*/
typedef enum {
CONV_COLUMN_SRC_ADDR,
CONV_COLUMN_SRC_PORT,
CONV_COLUMN_DST_ADDR,
CONV_COLUMN_DST_PORT,
CONV_COLUMN_PACKETS,
CONV_COLUMN_BYTES,
CONV_COLUMN_PKT_AB,
CONV_COLUMN_BYTES_AB,
CONV_COLUMN_PKT_BA,
CONV_COLUMN_BYTES_BA,
CONV_COLUMN_START,
CONV_COLUMN_DURATION,
CONV_COLUMN_BPS_AB,
CONV_COLUMN_BPS_BA,
CONV_NUM_COLUMNS,
CONV_INDEX_COLUMN = CONV_NUM_COLUMNS
} conversation_column_type_e;
extern const char *conv_column_titles[CONV_NUM_COLUMNS];
extern const char *conv_conn_a_title;
extern const char *conv_conn_b_title;
extern const char *conv_abs_start_title;
typedef enum
{
ENDP_COLUMN_ADDR,
ENDP_COLUMN_PORT,
ENDP_COLUMN_PACKETS,
ENDP_COLUMN_BYTES,
ENDP_COLUMN_PKT_AB,
ENDP_COLUMN_BYTES_AB,
ENDP_COLUMN_PKT_BA,
ENDP_COLUMN_BYTES_BA,
ENDP_NUM_COLUMNS,
ENDP_COLUMN_GEO_COUNTRY = ENDP_NUM_COLUMNS,
ENDP_COLUMN_GEO_CITY,
ENDP_COLUMN_GEO_AS_NUM,
ENDP_COLUMN_GEO_AS_ORG,
ENDP_NUM_GEO_COLUMNS
} endpoint_column_type_e;
extern const char *endp_column_titles[ENDP_NUM_GEO_COLUMNS];
extern const char *endp_conn_title;
#ifdef HAVE_MAXMINDDB
/**
* Writes an HTML file containing a map showing the geographical locations
* of IPv4 and IPv6 addresses.
*
* @param [in] fp File handle for writing the HTML file.
* @param [in] json_only Write GeoJSON data only.
* @param [in] hosts A NULL-terminated array of 'hostlist_talker_t'. A MMDB
* lookup should have been completed before for these addresses.
* @param [in,out] err_str Set to error string on failure. Error string must
* be g_freed. May be NULL.
* @return Whether the map file was successfully written with non-empty data.
*/
gboolean write_endpoint_geoip_map(FILE *fp, gboolean json_only, hostlist_talker_t *const *hosts, gchar **err_str);
#endif
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* __TRAFFIC_TABLE_UI_H__ */