From a102cd41165189d63727edf94100ddea5a57acab Mon Sep 17 00:00:00 2001 From: Chavdar Kirov Date: Fri, 28 Jan 2022 14:35:44 +0200 Subject: [PATCH] Support for reading Wireshark files Detection of Wireshark files implemented Guard statements for build on Linux pcaplite added Wireshark support works on Windows pcap support on Lunux fixed Build warning fixed Update pcaplite.cpp Cleanup Removed extra define --- SavvyCAN.pro | 6 +- framefileio.cpp | 96 ++++++++++++++++++++++ framefileio.h | 2 + pcaplite.cpp | 214 ++++++++++++++++++++++++++++++++++++++++++++++++ pcaplite.h | 32 ++++++++ 5 files changed, 348 insertions(+), 2 deletions(-) create mode 100644 pcaplite.cpp create mode 100644 pcaplite.h diff --git a/SavvyCAN.pro b/SavvyCAN.pro index ee9ac6f6..9df479e2 100644 --- a/SavvyCAN.pro +++ b/SavvyCAN.pro @@ -92,7 +92,8 @@ SOURCES += main.cpp\ re/sniffer/SnifferDelegate.cpp \ connections/newconnectiondialog.cpp \ re/temporalgraphwindow.cpp \ - filterutility.cpp + filterutility.cpp \ + pcaplite.cpp HEADERS += mainwindow.h \ can_structs.h \ @@ -178,7 +179,8 @@ HEADERS += mainwindow.h \ re/sniffer/SnifferDelegate.h \ connections/newconnectiondialog.h \ re/temporalgraphwindow.h \ - filterutility.h + filterutility.h \ + pcaplite.h FORMS += ui/candatagrid.ui \ ui/dbccomparatorwindow.ui \ diff --git a/framefileio.cpp b/framefileio.cpp index e9d0e818..5dc82d48 100644 --- a/framefileio.cpp +++ b/framefileio.cpp @@ -8,6 +8,7 @@ #include #include #include +#include "pcaplite.h" #include "utility.h" #include "blfhandler.h" @@ -189,6 +190,7 @@ bool FrameFileIO::loadFrameFile(QString &fileName, QVector* frameCache filters.append(QString(tr("Tesla Autopilot Snapshot (*.CAN *.can)"))); filters.append(QString(tr("CLX000 (*.txt *.TXT)"))); filters.append(QString(tr("CANServer Binary Log (*.log *.LOG)"))); + filters.append(QString(tr("Wireshark (*.pcap *.PCAP *.pcapng *.PCAPNG)"))); dialog.setDirectory(settings.value("FileIO/LoadSaveDirectory", dialog.directory().path()).toString()); dialog.setFileMode(QFileDialog::ExistingFile); @@ -234,6 +236,7 @@ bool FrameFileIO::loadFrameFile(QString &fileName, QVector* frameCache if (selectedNameFilter == filters[21]) result = loadTeslaAPFile(filename, frameCache); if (selectedNameFilter == filters[22]) result = loadCLX000File(filename, frameCache); if (selectedNameFilter == filters[23]) result = loadCANServerFile(filename, frameCache); + if (selectedNameFilter == filters[24]) result = loadWiresharkFile(filename, frameCache); progress.cancel(); @@ -305,6 +308,16 @@ bool FrameFileIO::autoDetectLoadFile(QString filename, QVector* frames } } + qDebug() << "Attempting Wireshark Log"; + if (isWiresharkFile(filename)) + { + if (loadWiresharkFile(filename, frames)) + { + qDebug() << "Loaded as Wireshark Log successfully!"; + return true; + } + } + qDebug() << "Attempting canalyzer ASC"; if (isCanalyzerASC(filename)) { @@ -4825,3 +4838,86 @@ bool FrameFileIO::loadCANServerFile(QString filename, QVector* frames) delete inFile; return !foundErrors; } + +bool FrameFileIO::loadWiresharkFile(QString filename, QVector* frames) +{ + pcap_t *pcap_data_file; + CANFrame thisFrame; + long long startTimestamp = 0; + long long timeStamp; + int lineCounter = 0; + bool foundErrors = false; + pcap_pkthdr packetHeader; + const char *packetData = NULL; + char errbuf[PCAP_ERRBUF_SIZE]; + + thisFrame.setFrameType(QCanBusFrame::DataFrame); + + QByteArray ba = filename.toLocal8Bit(); + + pcap_data_file = pcap_open_offline(ba.data(), errbuf); + if (!pcap_data_file) { + return false; + } + + packetData = (const char*)pcap_next(pcap_data_file, &packetHeader); + + while (packetData) { + lineCounter++; + if (lineCounter > 100) + { + qApp->processEvents(); + lineCounter = 0; + } + + timeStamp = packetHeader.ts.tv_sec * 1000000 + packetHeader.ts.tv_usec; + if (0 == startTimestamp) + { + startTimestamp = timeStamp; + } + + timeStamp -= startTimestamp; + + thisFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, timeStamp)); + thisFrame.isReceived = true; // TODO: check if tx detection is possible + + thisFrame.setFrameType(QCanBusFrame::DataFrame); + thisFrame.setFrameId((0xff & *(packetData+17)) << 8 | (0xff & *(packetData+16))); + if (thisFrame.frameId() <= 0x7FF) thisFrame.setExtendedFrameFormat(false); + else thisFrame.setExtendedFrameFormat(true); + thisFrame.bus = 0; + int numBytes = *(packetData+20); + QByteArray bytes(numBytes, 0); + if (thisFrame.payload().length() > 8) thisFrame.payload().resize(8); + for (int d = 0; d < numBytes; d++) + { + bytes[d] = *(packetData + 24 + d); + } + thisFrame.setPayload(bytes); + frames->append(thisFrame); + + packetData = (const char*)pcap_next(pcap_data_file, &packetHeader); + } + + pcap_close(pcap_data_file); + pcap_data_file = NULL; + + return !foundErrors; +} + +bool FrameFileIO::isWiresharkFile(QString filename) +{ + pcap_t *pcap_data_file; + char errbuf[PCAP_ERRBUF_SIZE]; + QByteArray ba = filename.toLocal8Bit(); + + pcap_data_file = pcap_open_offline(ba.data(), errbuf); + if (!pcap_data_file) { + return false; + } + + pcap_close(pcap_data_file); + pcap_data_file = NULL; + + return true; +} diff --git a/framefileio.h b/framefileio.h index 39f4d5fb..3d525174 100644 --- a/framefileio.h +++ b/framefileio.h @@ -53,6 +53,7 @@ class FrameFileIO: public QObject static bool loadTeslaAPFile(QString filename, QVector* frames); static bool loadCLX000File(QString filename, QVector* frames); static bool loadCANServerFile(QString filename, QVector* frames); + static bool loadWiresharkFile(QString filename, QVector* frames); //functions that pre-scan a file to try to figure out if they could read it. Used to automatically determine //file type and load it. @@ -78,6 +79,7 @@ class FrameFileIO: public QObject static bool isTeslaAPFile(QString filename); static bool isCLX000File(QString filename); static bool isCANServerFile(QString filename); + static bool isWiresharkFile(QString filename); static bool saveCRTDFile(QString, const QVector*); static bool saveNativeCSVFile(QString, const QVector*); diff --git a/pcaplite.cpp b/pcaplite.cpp new file mode 100644 index 00000000..27c72d42 --- /dev/null +++ b/pcaplite.cpp @@ -0,0 +1,214 @@ +#include +#include "pcaplite.h" + +#define MAGIC_NG 0x0A0D0D0A +#define MACIG 0xA1B2C3D4 + +#define MAX_CAN_PACKET_SIZE 256 + +// some pcap format constants +#define PCAP_FILE_HEADER_LENGTH 24 +#define PCAP_FRAME_HEADER_LENGTH 16 +#define PCAP_CAP_FRAME_LENGTH_OFFSET 8 +#define PCAP_FRAME_LENGTH_OFFSET 12 + +// some pcapng format constants +#define INTERFACE_DESCRITION_BLOCK 0x01 +#define ENCHANCED_PACKET_BLOCK 0x06 + +static unsigned char pcap_buffer[MAX_CAN_PACKET_SIZE]; +static pcap_t p; + +pcap *pcap_open_offline(const char *filename, char *error_text) { + FILE *file; + + snprintf(error_text, PCAP_ERRBUF_SIZE, "OK"); + + file = fopen(filename,"rb"); + + if (NULL == file) { + snprintf(error_text, PCAP_ERRBUF_SIZE, + "Cannot open input file"); + return NULL; + } + + unsigned int magic; + size_t bytes_read; + + bytes_read = fread(&magic, 1, sizeof(magic), file); + if (bytes_read != sizeof(magic)) { + snprintf(error_text, PCAP_ERRBUF_SIZE, "Cannot read magic word"); + fclose(file); + return (NULL); + } + + if (magic != MACIG && magic != MAGIC_NG) { + snprintf(error_text, PCAP_ERRBUF_SIZE, "Not a supported format %04x", magic); + fclose(file); + return (NULL); + } + + // set the format + // and seek past file header + if (MAGIC_NG == magic) { + p.is_ng = 1; + fseek(file, sizeof(magic), SEEK_SET); + unsigned int section_length; + bytes_read = fread(§ion_length, 1, sizeof(section_length), file); + if (bytes_read != sizeof(section_length)) { + snprintf(error_text, PCAP_ERRBUF_SIZE, "Cannot read section length"); + fclose(file); + return (NULL); + } + fseek(file, section_length, SEEK_SET); + + } else { + p.is_ng = 0; + fseek(file, PCAP_FILE_HEADER_LENGTH, SEEK_SET); + } + + p.file = file; + + return(&p); +} + +const unsigned char *pcap_next_ng(pcap_t *p, struct pcap_pkthdr *h) { + // this is for ENCHANCED_PACKET_BLOCK + struct block_header { + unsigned int block_type; + unsigned int block_size; + unsigned int interface_id; + unsigned int timestamp_hi; + unsigned int timestamp_lo; + unsigned int cap_len; + unsigned int len; + } bh; + + struct option_header { + unsigned short option_type; + unsigned short option_length; + } oh; + + size_t bytes_read; + + static double timestamp_multiplier = 0.000001; //microseconds resolution + + long fpos = ftell(p->file); + + do { + bytes_read = fread(&bh, 1, sizeof(bh), p->file); + if (bytes_read != sizeof(bh)) { + //probably EOF + return (NULL); + } + + if (bh.block_type != ENCHANCED_PACKET_BLOCK) { + if (INTERFACE_DESCRITION_BLOCK == bh.block_type) { + // Seek to start of options part in the header + fseek(p->file, fpos + 16, SEEK_SET); + do { + bytes_read = fread(&oh, 1, sizeof(oh), p->file); + if (bytes_read != sizeof(oh)) { + //probably EOF + return (NULL); + } + + // calcualte option length aligned on 4 byte boundary + unsigned int option_file_length = 4*(oh.option_length/4) + ((oh.option_length%4) ? 4:0); + + + // read in the buffer if it's large enough or skip + if (option_file_length <= sizeof(pcap_buffer)) { + bytes_read = fread(pcap_buffer, 1, option_file_length, p->file); + if (bytes_read != option_file_length) { + //probably EOF + return (NULL); + } + //check if time resolution option + if (0x09 == oh.option_type) { + unsigned int res = *(unsigned int*)pcap_buffer; + if ((0x80000000 & res) == 0) { + timestamp_multiplier = 1/pow(10, res); + } else { + timestamp_multiplier = 1/pow(2, (res & 0x7fffffff)); + } + } + } else if (fseek(p->file, option_file_length, SEEK_CUR)) { + //probably EOF + return (NULL); + } + + } while(ftell(p->file) < fpos + bh.block_size - sizeof(bh.block_size)); + + if (fseek(p->file, sizeof(bh.block_size), SEEK_CUR)) { + //probably EOF + return (NULL); + } + } + + // skip to the next block + if (fseek(p->file, fpos + bh.block_size, SEEK_SET)) { + //probably EOF + return (NULL); + } + fpos = ftell(p->file); + } + } while (bh.block_type != ENCHANCED_PACKET_BLOCK); + + h->caplen = bh.cap_len; + + double timestamp = ((unsigned long long)bh.timestamp_hi << 32 | bh.timestamp_lo) * timestamp_multiplier; + double fractional, integer; + + fractional = modf(timestamp, &integer); + h->ts.tv_sec = (long)integer; + h->ts.tv_usec = (long)(fractional * 1000000); + + bytes_read = fread(pcap_buffer, 1, h->caplen, p->file); + if (bytes_read != h->caplen) { + //probably EOF + return (NULL); + } + + //seek to the next block (go back with bytes read of the block and seek fwd with the block size) + if (fseek(p->file, fpos + bh.block_size, SEEK_SET)) { + //probably EOF + return (NULL); + } + + return pcap_buffer; +} + + +const unsigned char *pcap_next(pcap_t *p, struct pcap_pkthdr *h) +{ + if (p->is_ng) { + return pcap_next_ng(p, h); + } + + size_t bytes_read; + + bytes_read = fread(pcap_buffer, 1, PCAP_FRAME_HEADER_LENGTH, p->file); + if (bytes_read != PCAP_FRAME_HEADER_LENGTH) { + //probably EOF + return (NULL); + } + + h->ts.tv_sec = *(unsigned int*)(pcap_buffer); + h->ts.tv_usec = *(unsigned int*)(pcap_buffer + 4); + + h->caplen = *(unsigned int*)(pcap_buffer + PCAP_CAP_FRAME_LENGTH_OFFSET); + h->len = *(unsigned int*)(pcap_buffer + PCAP_FRAME_LENGTH_OFFSET); + + bytes_read = fread(pcap_buffer, 1, h->caplen, p->file); + if (bytes_read != h->caplen) { + //probably EOF + return (NULL); + } + + return pcap_buffer; +} + +void pcap_close(pcap_t *p) { + fclose(p->file); +} diff --git a/pcaplite.h b/pcaplite.h new file mode 100644 index 00000000..315fa5ac --- /dev/null +++ b/pcaplite.h @@ -0,0 +1,32 @@ +#ifndef PCAPLITE_H +#define PCAPLITE_H + +#include +#if defined(unix) +#include +#else +#include +#endif + +#define PCAP_ERRBUF_SIZE 256 + +struct pcap_pkthdr { + struct timeval ts; /* time stamp */ + unsigned int caplen; /* length of portion present */ + unsigned int len; /* length of this packet (off wire) */ +}; + +struct pcap { + FILE *file; + bool is_ng; +}; + +typedef struct pcap pcap_t; + +pcap *pcap_open_offline(const char *, char *); + +const unsigned char *pcap_next(pcap_t *, struct pcap_pkthdr *); + +void pcap_close(pcap_t *); + +#endif// PCAPLITE_H