diff options
author | Toni Uhlig <matzeton@googlemail.com> | 2023-07-09 10:52:21 +0200 |
---|---|---|
committer | Toni Uhlig <matzeton@googlemail.com> | 2023-07-10 10:52:21 +0200 |
commit | 84a337ae0a4b8f60220ac08d04222bbaff6143d0 (patch) | |
tree | a078350200483786002cdcd6258a7979809ba87e | |
parent | febaef00017278ac65cb7e285564ebc9d5dadfe5 (diff) |
MVP
Signed-off-by: Toni Uhlig <matzeton@googlemail.com>
-rw-r--r-- | .gitattributes | 1 | ||||
-rw-r--r-- | .gitmodules | 3 | ||||
-rw-r--r-- | CMakeLists.txt | 60 | ||||
m--------- | PcapPlusPlus | 0 | ||||
-rw-r--r-- | mainwindow.cpp | 114 | ||||
-rw-r--r-- | mainwindow.h | 10 | ||||
-rw-r--r-- | mainwindow.ui | 49 | ||||
-rw-r--r-- | pcapplusplus.cpp | 153 | ||||
-rw-r--r-- | pcapplusplus.h | 33 | ||||
-rw-r--r-- | qhexedit2/src/QHexEditPlugin.h | 6 |
10 files changed, 412 insertions, 17 deletions
diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..2420b0f --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +qhexedit2/* linguist-vendored diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..33b156f --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "PcapPlusPlus"] + path = PcapPlusPlus + url = https://github.com/seladb/PcapPlusPlus.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 3d462a4..2df0801 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,14 +9,52 @@ set(CMAKE_AUTORCC ON) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) -find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets) -find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets) +include(ExternalProject) + +find_package(QT NAMES Qt6 REQUIRED COMPONENTS Core Widgets) +find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core Widgets) +find_library(PCAP_LIBRARY NAMES pcap wpcap) + +set_property(SOURCE qhexedit2_init.cpp PROPERTY SKIP_AUTOGEN ON) + +qt_add_plugin(qhexedit2 + STATIC + qhexedit2/src/chunks.cpp + qhexedit2/src/chunks.h + qhexedit2/src/commands.cpp + qhexedit2/src/commands.h + qhexedit2/src/qhexedit.cpp + qhexedit2/src/qhexedit.h + qhexedit2/src/QHexEditPlugin.cpp + qhexedit2/src/QHexEditPlugin.h +) + +ExternalProject_Add(PcapPlusPlus SOURCE_DIR ${CMAKE_SOURCE_DIR}/PcapPlusPlus + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR}/ppp-install + -DBUILD_SHARED_LIBS=OFF -DPCAPPP_BUILD_COVERAGE=OFF + -DPCAPPP_BUILD_EXAMPLES=OFF -DPCAPPP_BUILD_TESTS=OFF) + +option(ENABLE_SANITIZER "Enable ASAN/LSAN/UBSAN." OFF) +if(ENABLE_SANITIZER) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -fsanitize=undefined -fno-sanitize=alignment -fsanitize=enum -fsanitize=leak") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fsanitize=undefined -fno-sanitize=alignment -fsanitize=enum -fsanitize=leak") +endif() set(PROJECT_SOURCES main.cpp mainwindow.cpp mainwindow.h mainwindow.ui + + pcapplusplus.cpp + pcapplusplus.h + + qhexedit2/src/chunks.cpp + qhexedit2/src/chunks.h + qhexedit2/src/commands.cpp + qhexedit2/src/commands.h + qhexedit2/src/qhexedit.cpp + qhexedit2/src/qhexedit.h ) if(${QT_VERSION_MAJOR} GREATER_EQUAL 6) @@ -24,17 +62,12 @@ if(${QT_VERSION_MAJOR} GREATER_EQUAL 6) MANUAL_FINALIZATION ${PROJECT_SOURCES} ) -# Define target properties for Android with Qt 6 as: -# set_property(TARGET pcap-editor APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR -# ${CMAKE_CURRENT_SOURCE_DIR}/android) -# For more information, see https://doc.qt.io/qt-6/qt-add-executable.html#target-creation + qt_import_plugins(pcap-editor INCLUDE qhexedit2) else() if(ANDROID) add_library(pcap-editor SHARED ${PROJECT_SOURCES} ) -# Define properties for Android with Qt 5 after find_package() calls as: -# set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android") else() add_executable(pcap-editor ${PROJECT_SOURCES} @@ -42,7 +75,14 @@ else() endif() endif() -target_link_libraries(pcap-editor PRIVATE Qt${QT_VERSION_MAJOR}::Widgets) +add_dependencies(pcap-editor PcapPlusPlus) + +target_include_directories(pcap-editor PRIVATE ${CMAKE_BINARY_DIR}/ppp-install/include/pcapplusplus) +target_link_libraries(pcap-editor PRIVATE Qt${QT_VERSION_MAJOR}::Widgets + ${CMAKE_BINARY_DIR}/ppp-install/lib/libPcap++.a + ${CMAKE_BINARY_DIR}/ppp-install/lib/libPacket++.a + ${CMAKE_BINARY_DIR}/ppp-install/lib/libCommon++.a + ${PCAP_LIBRARY}) set_target_properties(pcap-editor PROPERTIES MACOSX_BUNDLE_GUI_IDENTIFIER my.example.com @@ -59,3 +99,5 @@ install(TARGETS pcap-editor if(QT_VERSION_MAJOR EQUAL 6) qt_finalize_executable(pcap-editor) endif() + +message(STATUS "QT_DIR: ${QT_DIR}") diff --git a/PcapPlusPlus b/PcapPlusPlus new file mode 160000 +Subproject e96b741df40aa5e397916ab3c107ec2a57d37b8 diff --git a/mainwindow.cpp b/mainwindow.cpp index a06fc8e..2ff457a 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -1,15 +1,125 @@ #include "mainwindow.h" -#include "./ui_mainwindow.h" +#include "ui_mainwindow.h" + +#include <exception> +#include <qfiledialog.h> MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) + , myHexEdit() , ui(new Ui::MainWindow) + , ppp(nullptr) { ui->setupUi(this); + ui->gridLayout->addWidget(&myHexEdit); + + connect(ui->actionOpen, &QAction::triggered, this, [&](bool){ + QString fileName = QFileDialog::getOpenFileName(this, tr("Open PCAP File"), "", tr("PCAP Files (*.pcap)")); + if (fileName.length() > 0) { + if (ppp) { + delete ppp; + ppp = nullptr; + } + ppp = new PcapPlusPlus(fileName.toStdString()); + if (ppp) emit processPcap(); + } + }); + + connect(ui->actionSave, &QAction::triggered, this, [&](bool){ + QString fileName = QFileDialog::getSaveFileName(this, tr("Save PCAP File"), "", tr("PCAP Files (*.pcap)")); + if (fileName.length() > 0) { + pcpp::PcapFileWriterDevice pcapWriter(fileName.toStdString(), pcpp::LINKTYPE_ETHERNET); + if (!pcapWriter.open()) + throw std::runtime_error("Could not open file " + fileName.toStdString() + " for writing."); + { + for (auto rawPacket = ppp->rawPacketsBegin(); rawPacket < ppp->rawPacketsEnd(); rawPacket++) { + pcapWriter.writePacket(*rawPacket); + } + } + } + }); + + connect(this, &MainWindow::processPcap, this, [&](){ + pcpp::Packet packet; + if (!ppp) throw std::runtime_error("PcapPlusPlus was not initialized."); + firstPacketTs = 0; + while (ppp->processPacket(packet)) { + if (!firstPacketTs) + firstPacketTs = packet.getRawPacket()->getPacketTimeStamp().tv_sec; + emit onPacketAvailable(packet); + } + }); + + connect(this, &MainWindow::onPacketAvailable, this, [&](const pcpp::Packet& packet) { + ui->tableWidget->insertRow(ui->tableWidget->rowCount()); + + QTableWidgetItem* itemRelativeTime =new QTableWidgetItem(tr("%1").arg(packet.getRawPacket()->getPacketTimeStamp().tv_sec - firstPacketTs)); + ui->tableWidget->setItem(ui->tableWidget->rowCount() - 1, 0, itemRelativeTime); + + QTableWidgetItem* itemFrameLength = new QTableWidgetItem(tr("%1").arg(packet.getRawPacket()->getFrameLength())); + ui->tableWidget->setItem(ui->tableWidget->rowCount() - 1, 1, itemFrameLength); + + const auto *firstLayer = PcapPlusPlus::getFirstLayer(packet); + + QTableWidgetItem* itemFirstLayerProtocol = new QTableWidgetItem(tr("%1").arg(firstLayer ? PcapPlusPlus::getProtocolTypeAsString(firstLayer->getProtocol()) : "")); + ui->tableWidget->setItem(ui->tableWidget->rowCount() - 1, 2, itemFirstLayerProtocol); + + const auto *secondLayer = firstLayer ? firstLayer->getNextLayer() : nullptr; + + QTableWidgetItem* itemSecondLayerProtocol = new QTableWidgetItem(tr("%1").arg(secondLayer ? PcapPlusPlus::getProtocolTypeAsString(secondLayer->getProtocol()) : "")); + ui->tableWidget->setItem(ui->tableWidget->rowCount() - 1, 3, itemSecondLayerProtocol); + + const auto *thirdLayer = secondLayer ? secondLayer->getNextLayer() : nullptr; + + QTableWidgetItem* itemThirdLayerProtocol = new QTableWidgetItem(tr("%1").arg(thirdLayer ? PcapPlusPlus::getProtocolTypeAsString(thirdLayer->getProtocol()) : "")); + ui->tableWidget->setItem(ui->tableWidget->rowCount() - 1, 4, itemThirdLayerProtocol); + + const auto ethTuple = PcapPlusPlus::getEthTuple(packet); + + QTableWidgetItem* itemSrcMac = new QTableWidgetItem(tr("%1").arg(std::get<0>(ethTuple))); + ui->tableWidget->setItem(ui->tableWidget->rowCount() - 1, 5, itemSrcMac); + + QTableWidgetItem* itemDstMac = new QTableWidgetItem(tr("%1").arg(std::get<1>(ethTuple))); + ui->tableWidget->setItem(ui->tableWidget->rowCount() - 1, 6, itemDstMac); + + const auto ipTuple = PcapPlusPlus::getIpTuple(packet); + + QTableWidgetItem* itemSrcIp = new QTableWidgetItem(tr("%1").arg(std::get<0>(ipTuple))); + ui->tableWidget->setItem(ui->tableWidget->rowCount() - 1, 7, itemSrcIp); + + QTableWidgetItem* itemDstIp = new QTableWidgetItem(tr("%1").arg(std::get<1>(ipTuple))); + ui->tableWidget->setItem(ui->tableWidget->rowCount() - 1, 8, itemDstIp); + + const auto l4Tuple = PcapPlusPlus::getLayer4Tuple(packet); + + QTableWidgetItem* itemSrcPort = new QTableWidgetItem(tr("%1").arg(std::get<0>(l4Tuple))); + ui->tableWidget->setItem(ui->tableWidget->rowCount() - 1, 9, itemSrcPort); + + QTableWidgetItem* itemDstPort = new QTableWidgetItem(tr("%1").arg(std::get<1>(l4Tuple))); + ui->tableWidget->setItem(ui->tableWidget->rowCount() - 1, 10, itemDstPort); + + QTableWidgetItem* itemDesc = new QTableWidgetItem(tr("%1").arg(QString::fromStdString(thirdLayer ? thirdLayer->toString() : (secondLayer ? secondLayer->toString() : (firstLayer ? firstLayer->toString() : ""))))); + ui->tableWidget->setItem(ui->tableWidget->rowCount() - 1, 11, itemDesc); + }); + + connect(ui->tableWidget, &QTableWidget::cellPressed, this, [&] { + const auto &selected = ui->tableWidget->selectedItems(); + const auto &rawPacket = ppp->getRawPacket(selected.last()->row()); + myHexEdit.setData(QByteArray::fromRawData(reinterpret_cast<const char *>(rawPacket.getRawData()), rawPacket.getRawDataLen())); + }); + + connect(&myHexEdit, &QHexEdit::dataChanged, this, [&] { + const auto &selected = ui->tableWidget->selectedItems(); + auto &rawPacket = ppp->getRawPacket(selected.last()->row()); + const auto cursorPos = myHexEdit.cursorPosition() / 2; + const auto cursorData = myHexEdit.dataAt(cursorPos, 1); + rawPacket.removeData(cursorPos, 1); + rawPacket.insertData(cursorPos, reinterpret_cast<const uint8_t *>(cursorData.data()), cursorData.size()); + }); } MainWindow::~MainWindow() { delete ui; + delete ppp; } - diff --git a/mainwindow.h b/mainwindow.h index 4643e32..242ca65 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -1,6 +1,9 @@ #ifndef MAINWINDOW_H #define MAINWINDOW_H +#include "pcapplusplus.h" +#include "qhexedit2/src/qhexedit.h" + #include <QMainWindow> QT_BEGIN_NAMESPACE @@ -16,6 +19,13 @@ public: ~MainWindow(); private: + QHexEdit myHexEdit; + time_t firstPacketTs; Ui::MainWindow *ui; + PcapPlusPlus *ppp; + +signals: + void processPcap(); + void onPacketAvailable(const pcpp::Packet& packet); }; #endif // MAINWINDOW_H diff --git a/mainwindow.ui b/mainwindow.ui index 7797e9f..ea1b3a3 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -42,7 +42,42 @@ <item> <layout class="QGridLayout" name="gridLayout"> <item row="0" column="0"> - <widget class="QListWidget" name="listWidget"/> + <widget class="QTableWidget" name="tableWidget"> + <property name="styleSheet"> + <string notr="true">color: black; +background-color: white; +selection-color: yellow; +selection-background-color: grey; + +border-style: outset; +border-width: 2px; +border-color: beige;</string> + </property> + <property name="selectionBehavior"> + <enum>QAbstractItemView::SelectRows</enum> + </property> + <property name="columnCount"> + <number>12</number> + </property> + <attribute name="horizontalHeaderCascadingSectionResizes"> + <bool>false</bool> + </attribute> + <attribute name="horizontalHeaderStretchLastSection"> + <bool>true</bool> + </attribute> + <column/> + <column/> + <column/> + <column/> + <column/> + <column/> + <column/> + <column/> + <column/> + <column/> + <column/> + <column/> + </widget> </item> </layout> </item> @@ -61,10 +96,22 @@ <property name="title"> <string>File</string> </property> + <addaction name="actionOpen"/> + <addaction name="actionSave"/> </widget> <addaction name="menuFile"/> </widget> <widget class="QStatusBar" name="statusbar"/> + <action name="actionOpen"> + <property name="text"> + <string>Open</string> + </property> + </action> + <action name="actionSave"> + <property name="text"> + <string>Save</string> + </property> + </action> </widget> <resources/> <connections/> diff --git a/pcapplusplus.cpp b/pcapplusplus.cpp new file mode 100644 index 0000000..894d799 --- /dev/null +++ b/pcapplusplus.cpp @@ -0,0 +1,153 @@ +#include "pcapplusplus.h" + +#include <EthLayer.h> +#include <exception> +#include <IPLayer.h> +#include <TcpLayer.h> +#include <tuple> +#include <UdpLayer.h> + +PcapPlusPlus::PcapPlusPlus(std::string fileName) : rawPackets(), parsedPackets() +{ + reader = pcpp::IFileReaderDevice::getReader(fileName); + + if (!reader) { + std::string err("PCAP Reader failed for: " + fileName); + throw std::runtime_error(err); + } + + if (!reader->open()) { + std::string err("PCAP Open failed for: " + fileName); + throw std::runtime_error(err); + } +} + +PcapPlusPlus::~PcapPlusPlus() +{ + if (reader) { + reader->close(); + delete reader; + reader = nullptr; + } +} + +bool PcapPlusPlus::processPacket(pcpp::Packet & packet) +{ + pcpp::RawPacket rawPacket; + + if (!reader) + return false; + + if (reader->getNextPacket(rawPacket)) { + rawPackets.emplace_back(std::move(rawPacket)); + + pcpp::Packet parsedPacket(&rawPackets.back(), false); + packet = parsedPacket; + + parsedPackets.emplace_back(std::move(parsedPacket)); + + return true; + } + + return false; +} + +pcpp::RawPacket &PcapPlusPlus::getRawPacket(size_t index) +{ + return rawPackets.at(index); +} + +pcpp::Packet &PcapPlusPlus::getParsedPacket(size_t index) +{ + return parsedPackets.at(index); +} + +std::vector<pcpp::RawPacket>::iterator PcapPlusPlus::rawPacketsBegin() +{ + return rawPackets.begin(); +} + +std::vector<pcpp::RawPacket>::iterator PcapPlusPlus::rawPacketsEnd() +{ + return rawPackets.end(); +} + +std::vector<pcpp::Packet>::iterator PcapPlusPlus::parsedPacketsBegin() +{ + return parsedPackets.begin(); +} + +std::vector<pcpp::Packet>::iterator PcapPlusPlus::parsedPacketsEnd() +{ + return parsedPackets.end(); +} + +const pcpp::Layer *PcapPlusPlus::getFirstLayer(const pcpp::Packet & packet) +{ + const auto *curLayer = packet.getFirstLayer(); + + if (curLayer == nullptr) + { + throw std::runtime_error("No Ethernet Layer found."); + return nullptr; + } + + return curLayer; +} + +QString PcapPlusPlus::getProtocolTypeAsString(pcpp::ProtocolType protocolType) +{ + switch (protocolType) + { + case pcpp::Ethernet: + return "Ethernet"; + case pcpp::ARP: + return "ARP"; + case pcpp::IPv4: + return "IPv4"; + case pcpp::IPv6: + return "IPv6"; + case pcpp::TCP: + return "TCP"; + case pcpp::UDP: + return "UDP"; + case pcpp::HTTPRequest: + case pcpp::HTTPResponse: + return "HTTP"; + case pcpp::UnknownProtocol: + return "Unknown Protocol"; + default: + return "Unknown"; + } +} + +std::tuple<QString, QString> PcapPlusPlus::getEthTuple(const pcpp::Packet & packet) +{ + const auto *ethernetLayer = packet.getLayerOfType<pcpp::EthLayer>(); + if (ethernetLayer == NULL) + return std::make_tuple("", ""); + + return std::make_tuple(QString::fromStdString(ethernetLayer->getSourceMac().toString()), QString::fromStdString(ethernetLayer->getDestMac().toString())); +} + +std::tuple<QString, QString> PcapPlusPlus::getIpTuple(const pcpp::Packet & packet) +{ + const auto *ipLayer = packet.getLayerOfType<pcpp::IPLayer>(); + if (ipLayer == NULL) + return std::make_tuple("", ""); + + return std::make_tuple(QString::fromStdString(ipLayer->getSrcIPAddress().toString()), QString::fromStdString(ipLayer->getDstIPAddress().toString())); +} + +std::tuple<uint16_t, uint16_t> PcapPlusPlus::getLayer4Tuple(const pcpp::Packet & packet) +{ + const auto *udpLayer = packet.getLayerOfType<pcpp::UdpLayer>(); + if (udpLayer != NULL) + return std::make_tuple(udpLayer->getSrcPort(), udpLayer->getDstPort()); + + const auto *tcpLayer = packet.getLayerOfType<pcpp::TcpLayer>(); + if (tcpLayer != NULL) + return std::make_tuple(tcpLayer->getSrcPort(), tcpLayer->getDstPort()); + + return std::make_tuple(0, 0); +} diff --git a/pcapplusplus.h b/pcapplusplus.h new file mode 100644 index 0000000..4afc7ff --- /dev/null +++ b/pcapplusplus.h @@ -0,0 +1,33 @@ +#ifndef PCAPPLUSPLUS_H +#define PCAPPLUSPLUS_H + +#include <Packet.h> +#include <PcapFileDevice.h> +#include <qstring.h> + +class PcapPlusPlus +{ +public: + explicit PcapPlusPlus(std::string fileName); + ~PcapPlusPlus(); + bool processPacket(pcpp::Packet & packet); + pcpp::RawPacket &getRawPacket(size_t index); + pcpp::Packet &getParsedPacket(size_t index); + std::vector<pcpp::RawPacket>::iterator rawPacketsBegin(); + std::vector<pcpp::RawPacket>::iterator rawPacketsEnd(); + std::vector<pcpp::Packet>::iterator parsedPacketsBegin(); + std::vector<pcpp::Packet>::iterator parsedPacketsEnd(); + + static const pcpp::Layer *getFirstLayer(const pcpp::Packet & packet); + static QString getProtocolTypeAsString(pcpp::ProtocolType protocolType); + static std::tuple<QString, QString> getEthTuple(const pcpp::Packet & packet); + static std::tuple<QString, QString> getIpTuple(const pcpp::Packet & packet); + static std::tuple<uint16_t, uint16_t> getLayer4Tuple(const pcpp::Packet & packet); + +private: + pcpp::IFileReaderDevice *reader; + std::vector<pcpp::RawPacket> rawPackets; + std::vector<pcpp::Packet> parsedPackets; +}; + +#endif // PCAPPLUSPLUS_H diff --git a/qhexedit2/src/QHexEditPlugin.h b/qhexedit2/src/QHexEditPlugin.h index a67179c..a1a55bb 100644 --- a/qhexedit2/src/QHexEditPlugin.h +++ b/qhexedit2/src/QHexEditPlugin.h @@ -3,11 +3,7 @@ #include <QObject> -#if QT_VERSION < QT_VERSION_CHECK(5,0,0) -#include <QtDesigner/QDesignerCustomWidgetInterface> -#else -#include <QDesignerCustomWidgetInterface> -#endif +#include <QtUiPlugin/QDesignerCustomWidgetInterface> class QHexEditPlugin : public QObject, public QDesignerCustomWidgetInterface { |