From b8789d2a8400e20c84a4aa09a46ac481199f625f Mon Sep 17 00:00:00 2001 From: alex Date: Thu, 22 Aug 2024 14:02:18 +0800 Subject: [PATCH 1/4] feat: dump pcap file --- include/mainwindow.h | 7 +++- include/packetsource.h | 17 +++++++- include/trayicon.h | 1 + src/packet/packetsource.cpp | 82 ++++++++++++++++++++++++++++++------- src/trayicon.cpp | 5 +++ src/window/mainwindow.cpp | 43 +++++++++++++++++-- 6 files changed, 133 insertions(+), 22 deletions(-) diff --git a/include/mainwindow.h b/include/mainwindow.h index 65e1e46..f35480d 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -35,8 +35,8 @@ class MainWindow : public QMainWindow { ~MainWindow() override; void changeInterfaceIndex(int index) const; void freePackets(); - void captureInterfaceStarted(string name, string message); - void captureInterfaceStopped(string name, string message) const; + void captureInterfaceStarted(packetsource_state state); + void captureInterfaceStopped(packetsource_state state) const; void resetCapture(); void acceptPacket(int index, Packet* p) const; void initSlots(); @@ -48,6 +48,7 @@ class MainWindow : public QMainWindow { void initInterfaceList(); void about(); void activateStatsWindow() const; + void saveAsPcap(); void initMenus(); private: @@ -71,6 +72,8 @@ class MainWindow : public QMainWindow { QMenu* helpMenu = nullptr; QMenu* windowMenu = nullptr; QAction* loadFileAct = nullptr; + QAction* saveAct = nullptr; + QAction* dumpFilename = nullptr; QAction* aboutAct = nullptr; QAction* statsAct = nullptr; QAction* aboutQtAct = nullptr; diff --git a/include/packetsource.h b/include/packetsource.h index d22530a..bc98203 100644 --- a/include/packetsource.h +++ b/include/packetsource.h @@ -4,20 +4,29 @@ #include #include +typedef struct packetsource_state { + string interface_name; + string state; + string dump_filename; +} PACKETSOURCE_STATE; + class PacketSource final : public QThread { Q_OBJECT vector* packetsPtr; pcap_t* interface = nullptr; pcap_if_t* device = nullptr; + string dump_filename; + pcap_dumper_t* dump_handler = nullptr; string filename; bool running = false; void run() override; + void dump_flush(const pcap_pkthdr*, const u_char*) const; static int parse_header(const u_char**, Packet*& p); signals: - void listen_started(std::string name, std::string message) const; - void listen_stopped(std::string name, std::string message) const; + void listen_started(PACKETSOURCE_STATE) const; + void listen_stopped(PACKETSOURCE_STATE) const; void captured(size_t, Packet*); public: @@ -27,9 +36,13 @@ class PacketSource final : public QThread { { } + ~PacketSource(); + + void clean_last_dump(); void init(pcap_if_t* device, pcap_t* interface); [[nodiscard]] string get_filename() const; void set_filename(const string& filename); [[nodiscard]] pcap_t* get_interface() const; + [[nodiscard]] string get_dump_filename() const; void free(); }; diff --git a/include/trayicon.h b/include/trayicon.h index 6d3ccf6..1f828a9 100644 --- a/include/trayicon.h +++ b/include/trayicon.h @@ -10,5 +10,6 @@ class TrayIcon : public QWidget { public: explicit TrayIcon(QSystemTrayIcon* t, QWidget* wdi); ~TrayIcon(); + void showMessage(const std::string& title, const std::string& message) const; void onActivated(QSystemTrayIcon::ActivationReason reason) const; }; diff --git a/src/packet/packetsource.cpp b/src/packet/packetsource.cpp index dd3058b..76e3bdc 100644 --- a/src/packet/packetsource.cpp +++ b/src/packet/packetsource.cpp @@ -9,30 +9,44 @@ #include "utils.h" #include #include +#include <__filesystem/operations.h> +#include #include using namespace std; -void PacketSource::init(pcap_if_t* device, pcap_t* interface) +PacketSource::~PacketSource() { - this->device = device; - this->interface = interface; - this->running = true; + free(); + clean_last_dump(); } -string PacketSource::get_filename() const +void PacketSource::clean_last_dump() { - return filename; -} + if (this->dump_filename.empty()) { + return; + } -void PacketSource::set_filename(const string& filename) -{ - this->filename = filename; + std::filesystem::remove(this->dump_filename); + LOG(INFO) << std::format("remove {}", this->dump_filename); + this->dump_filename = ""; } -pcap_t* PacketSource::get_interface() const +void PacketSource::init(pcap_if_t* device, pcap_t* interface) { - return interface; + this->device = device; + this->interface = interface; + this->running = true; + + clean_last_dump(); + if (device != nullptr) { + dump_filename = std::format("/tmp/{}.{}.pcap", device->name, std::time(0)); + } else { + dump_filename = std::format("/tmp/{}.pcap", std::time(0)); + } + + LOG(INFO) << std::format("open dump handler, filename: {}", dump_filename); + this->dump_handler = pcap_dump_open(interface, dump_filename.c_str()); } void PacketSource::free() @@ -47,13 +61,40 @@ void PacketSource::free() if (this->device != nullptr) { this->device = nullptr; } + + if (this->dump_handler != nullptr) { + pcap_dump_close(this->dump_handler); + this->dump_handler = nullptr; + } +} +string PacketSource::get_dump_filename() const +{ + return dump_filename; +} + +string PacketSource::get_filename() const +{ + return filename; +} + +void PacketSource::set_filename(const string& filename) +{ + this->filename = filename; +} + +pcap_t* PacketSource::get_interface() const +{ + return interface; } void PacketSource::run() { const string name = this->device ? this->device->name : this->filename; - emit listen_started(name, "on"); + emit listen_started({ .dump_filename = dump_filename, + .interface_name = name, + .state = "on" }); + while (true) { if (!running || this->interface == nullptr) { break; @@ -76,11 +117,24 @@ void PacketSource::run() p->set_payload(&pkt_data); packetsPtr->push_back(p); + dump_flush(pkt_header, pkt_data); // 如果并发,会有线程安全问题,size 不准 emit this->captured(packetsPtr->size() - 1, p); } - emit this->listen_stopped(name, "off"); + emit listen_started({ .dump_filename = dump_filename, + .interface_name = name, + .state = "off" }); +} + +void PacketSource::dump_flush(const pcap_pkthdr* h, const u_char* sp) const +{ + if (dump_handler == nullptr) { + return; + } + + pcap_dump(reinterpret_cast(dump_handler), h, sp); + pcap_dump_flush(dump_handler); } int PacketSource::parse_header(const u_char** pkt_data, Packet*& p) diff --git a/src/trayicon.cpp b/src/trayicon.cpp index c18dff5..2b5bbef 100644 --- a/src/trayicon.cpp +++ b/src/trayicon.cpp @@ -17,6 +17,11 @@ TrayIcon::~TrayIcon() delete wdi; } +void TrayIcon::showMessage(const std::string& title, const std::string& message) const +{ + tray->showMessage(title.c_str(), message.c_str()); +} + void TrayIcon::onActivated(QSystemTrayIcon::ActivationReason reason) const { // 图标的位置的 x 是屏幕最左侧到图标最左侧的距离 diff --git a/src/window/mainwindow.cpp b/src/window/mainwindow.cpp index 17daeb3..06a6f3d 100644 --- a/src/window/mainwindow.cpp +++ b/src/window/mainwindow.cpp @@ -107,6 +107,8 @@ MainWindow::~MainWindow() delete helpMenu; delete windowMenu; delete statsAct; + delete saveAct; + delete dumpFilename; delete loadFileAct; delete aboutAct; delete aboutQtAct; @@ -133,10 +135,14 @@ void MainWindow::freePackets() vector().swap(packets); } -void MainWindow::captureInterfaceStarted(string name, string message) +void MainWindow::captureInterfaceStarted(packetsource_state state) { freePackets(); + if (!state.dump_filename.empty()) { + dumpFilename->setText(state.dump_filename.c_str()); + } + time_start = std::chrono::high_resolution_clock::now(); ui->interfaceList->setDisabled(true); ui->startBtn->setDisabled(false); @@ -148,16 +154,16 @@ void MainWindow::captureInterfaceStarted(string name, string message) ui->hexTable->setRowCount(0); ui->layerTree->clear(); - interfaceStatusLabel->setText(name.append(": ").append(message).c_str()); + interfaceStatusLabel->setText(state.interface_name.append(": ").append(state.state).c_str()); updateCaptureStatusLabel(); } -void MainWindow::captureInterfaceStopped(string name, string message) const +void MainWindow::captureInterfaceStopped(packetsource_state state) const { ui->interfaceList->setDisabled(false); ui->resetBtn->setDisabled(true); ui->startBtn->setText("Start"); - interfaceStatusLabel->setText(name.append(": ").append(message).c_str()); + interfaceStatusLabel->setText(state.interface_name.append(": ").append(state.state).c_str()); updateCaptureStatusLabel(); } @@ -658,6 +664,23 @@ void MainWindow::activateStatsWindow() const statsWindow->activateWindow(); } +void MainWindow::saveAsPcap() +{ + if (packetSource->get_dump_filename().empty()) { + QMessageBox::warning(this, "", "no dump file"); + return; + } + + string basedir = QStandardPaths::standardLocations(QStandardPaths::DocumentsLocation)[0] + .toStdString(); + + QString fileName = QFileDialog::getSaveFileName(this, "Save document", basedir.c_str(), "*.pcap"); + if (!fileName.isEmpty()) { + std::filesystem::rename(packetSource->get_dump_filename(), fileName.toStdString()); + trayIcon->showMessage("INFO", std::format("Save complete: {}", fileName.toStdString())); + } +} + void MainWindow::initMenus() { // 加载本地 pcap 文件,该功能可以调用,但会立即崩溃 @@ -665,6 +688,14 @@ void MainWindow::initMenus() loadFileAct->setShortcuts(QKeySequence::Open); connect(loadFileAct, &QAction::triggered, this, &MainWindow::loadOfflineFile); + // 本质上就是把捕获的 pcap 文件移动到新路径 + saveAct = new QAction(tr("&Dump File"), this); + saveAct->setShortcuts(QKeySequence::SaveAs); + connect(saveAct, &QAction::triggered, this, &MainWindow::saveAsPcap); + + dumpFilename = new QAction("Wait Start.", this); + dumpFilename->setDisabled(true); + // 打开统计视图 statsAct = new QAction(tr("&Statistics"), this); connect(statsAct, &QAction::triggered, this, &MainWindow::activateStatsWindow); @@ -679,6 +710,10 @@ void MainWindow::initMenus() // 如果 Action 在默认菜单列已经实现,则不会显示该 Action fileMenu = new QMenu(tr("&File"), this); fileMenu->addAction(loadFileAct); + fileMenu->addAction(saveAct); + fileMenu->addAction(saveAct); + fileMenu->addSeparator(); + fileMenu->addAction(dumpFilename); // Help 在 Mac 下会被合并到默认菜单列 helpMenu = new QMenu(tr("&Help"), this); From 1d4705e75b5829039e3969bc93cfe113c8e7a720 Mon Sep 17 00:00:00 2001 From: alex Date: Fri, 23 Aug 2024 12:42:23 +0800 Subject: [PATCH 2/4] fix: large traffic crash --- CMakeLists.txt | 15 +++- hack/build.sh | 2 + include/mainwindow.h | 2 - include/packet.h | 6 +- include/packetsource.h | 88 ++++++++++++++++++---- src/conf.cpp | 12 ++- src/packet/packet.cpp | 12 ++- src/packet/packetsource.cpp | 131 ++++++++++++++++++++++++++------ src/window/mainwindow.cpp | 144 +++++++++++++++--------------------- 9 files changed, 283 insertions(+), 129 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 85b4d2c..79d8b2b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,6 +6,8 @@ set(CMAKE_AUTORCC ON) set(CMAKE_AUTOUIC ON) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_VERBOSE_MAKEFILE ON) + # 无法自动加载时,设置 QT 目录 #set(Qt6_DIR $ENV{HOME}/Qt6/6.7.2/macos/lib/cmake/Qt6) @@ -55,9 +57,18 @@ if (CMAKE_BUILD_TYPE STREQUAL "Debug") add_definitions(-DDEBUG_BUILD) endif () +if (CMAKE_BUILD_TYPE STREQUAL "Release") + # 为 release 模式设置优化选型 + # -O3:开启最高级别的优化 + # -march=native:针对构建机器的本地架构进行优化(这可能会生成仅在类似机器上运行良好的代码) + # -ffast-math:允许编译器在浮点数运算中使用可能违反 IEEE 标准的优化,能显著提高执行速度,但可能会破坏浮点数的精确度或兼容性 + # -DNDEBUG:定义 NDEBUG 宏,这通常会关闭 assert() 语句(如果你在使用它们的话) + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3 -march=native -ffast-math -DNDEBUG") +endif () + # qt core -find_package(Qt6 COMPONENTS Core Gui Widgets Charts PrintSupport REQUIRED) -target_link_libraries(${PROJECT_NAME} PRIVATE Qt::Core Qt::Gui Qt::Charts Qt::Widgets Qt::PrintSupport) +find_package(Qt6 COMPONENTS Core Gui Widgets PrintSupport REQUIRED) +target_link_libraries(${PROJECT_NAME} PRIVATE Qt::Core Qt::Gui Qt::Widgets Qt::PrintSupport) # glog find_package(glog CONFIG REQUIRED) target_link_libraries(${PROJECT_NAME} PRIVATE glog::glog) diff --git a/hack/build.sh b/hack/build.sh index be4eba0..f19358a 100755 --- a/hack/build.sh +++ b/hack/build.sh @@ -25,3 +25,5 @@ cd .. mv ${CMAKE_BUILD_DIR}/*.dmg . rm -rf ${CMAKE_BUILD_DIR} + +echo -e "use Console.app to watch WireDolphin Log" diff --git a/include/mainwindow.h b/include/mainwindow.h index f35480d..ce73647 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -34,7 +34,6 @@ class MainWindow : public QMainWindow { explicit MainWindow(QWidget* parent = nullptr); ~MainWindow() override; void changeInterfaceIndex(int index) const; - void freePackets(); void captureInterfaceStarted(packetsource_state state); void captureInterfaceStopped(packetsource_state state) const; void resetCapture(); @@ -55,7 +54,6 @@ class MainWindow : public QMainWindow { Ui::MainWindow* ui; pcap_if_t* allDevs = nullptr; PacketSource* packetSource; - vector packets; bool captureStart = false; QLabel* interfaceStatusLabel = new QLabel("", this); QLabel* captureStatusLabel = new QLabel("", this); diff --git a/include/packet.h b/include/packet.h index 2cff1ec..7f5e4d0 100644 --- a/include/packet.h +++ b/include/packet.h @@ -1,6 +1,5 @@ #pragma once -#include #include #include #include @@ -23,6 +22,7 @@ class Packet { int ip_version = 0; u_short type_flag {}; u_char payload[2048] = {}; + pcap_pkthdr* header = nullptr; ipv4_header* ipv4 = nullptr; ipv6_header* ipv6 = nullptr; tcp_header* tcp = nullptr; @@ -30,6 +30,7 @@ class Packet { udp_header* udp = nullptr; tcp_flags* flags = nullptr; string time; + timeval* time_ts = nullptr; string link_src; string link_dst; string host_src; @@ -62,6 +63,7 @@ class Packet { [[nodiscard]] vector get_color() const; [[nodiscard]] arp_header* get_arp() const; [[nodiscard]] udp_header* get_udp() const; + [[nodiscard]] pcap_pkthdr* get_header() const; void set_udp(udp_header* udp); void set_arp(arp_header* arp); void set_port_src(int port_src); @@ -79,7 +81,7 @@ class Packet { void set_type(const string& type); void set_len(int len, int caplen); void set_info(const string& info); - void set_time(const string& time); + void set_header(pcap_pkthdr* hdr); void set_type_flag(u_short); void set_payload(const u_char** pkt_data); }; diff --git a/include/packetsource.h b/include/packetsource.h index bc98203..dd46849 100644 --- a/include/packetsource.h +++ b/include/packetsource.h @@ -3,6 +3,15 @@ #include "packet.h" #include #include +#include + +// 更新表格超时周期 +#define DEFAULT_QUEUE_UPDATE_TIMEOUT_MS 10 +// 数字越小,小流量更新频率越快,实时性越高 +#define DEFAULT_PERIOD_AVERAGE 5 +// 10ms 内更新 100 条,一秒就会更新1万条,表格可能来不及更新就会卡死 +#define AVERAGE_PERIOD(v) \ + (v > 100 ? v * 5 : (v > 50 ? v * 3 : (v > DEFAULT_PERIOD_AVERAGE ? v : DEFAULT_PERIOD_AVERAGE))) typedef struct packetsource_state { string interface_name; @@ -10,19 +19,73 @@ typedef struct packetsource_state { string dump_filename; } PACKETSOURCE_STATE; -class PacketSource final : public QThread { +class PacketSource final : public QObject { Q_OBJECT - vector* packetsPtr; + /** + * 全局互斥锁 + */ + std::mutex mtx; + /** + * 包队列 + * 用于在捕获线程和渲染线程之间转移包 + * 捕获后的包会先到该队列,每隔一段时间会被消费和传递到UI线程进行表格渲染 + */ + std::queue bridge; + /* + * 存储捕获的所有的包 + * 要在开始或重置时清空,否则将内存泄漏 + */ + std::vector history; + /* + * 记录队列上一次被清空的时间 + */ + std::chrono::steady_clock::time_point last_access; + /** + * 时间段内捕获的包的平均数 + * 做到推送的行数不恒定,流量越大推送频率越低,但一次推送的数量越多 + */ + int period_average = DEFAULT_PERIOD_AVERAGE; + /** + * 填充桥的线程 + */ + std::thread fill_thread; + /** + * 消费桥的线程 + * 都在析构和停止捕获时进行 join + */ + std::thread consume_thread; + /** + * 捕获的接口的 pcap 实例 + */ pcap_t* interface = nullptr; + /** + * 捕获的接口的设备实例,目前只是从里面取出名字,没有其他用途 + */ pcap_if_t* device = nullptr; + /** + * 当前一次捕获到的包暂存文件的文件名 + * 当启动捕获时,该文件会被删除和重建 + * 当选择 Menu 中的 Dump File 时将会被移动到用户选择的目录 + */ string dump_filename; + /** + * pcap 的写入 pcap 文件的句柄 + */ pcap_dumper_t* dump_handler = nullptr; + /** + * 如果当前并不是实时捕获,而是打开了一个文件,就会填充改文件名 + */ string filename; + /** + * 控制是否继续接收和处理包 + */ bool running = false; - void run() override; + + void capture_packet(); void dump_flush(const pcap_pkthdr*, const u_char*) const; static int parse_header(const u_char**, Packet*& p); + void consume_queue(); signals: void listen_started(PACKETSOURCE_STATE) const; @@ -30,19 +93,16 @@ class PacketSource final : public QThread { void captured(size_t, Packet*); public: - explicit PacketSource(QObject* parent = nullptr, vector* packets = nullptr) - : QThread(parent) - , packetsPtr(packets) - { - } - + explicit PacketSource(); ~PacketSource(); - void clean_last_dump(); - void init(pcap_if_t* device, pcap_t* interface); - [[nodiscard]] string get_filename() const; + void free_wait(); void set_filename(const string& filename); - [[nodiscard]] pcap_t* get_interface() const; + void start_on_interface(pcap_if_t* device, pcap_t* interface); + + [[nodiscard]] string get_filename() const; + [[nodiscard]] pcap_t* get_interface() const;void free_history(); [[nodiscard]] string get_dump_filename() const; - void free(); + [[nodiscard]] size_t packet_count() const; + [[nodiscard]] Packet* peek(int index) const; }; diff --git a/src/conf.cpp b/src/conf.cpp index 5862494..13075c9 100644 --- a/src/conf.cpp +++ b/src/conf.cpp @@ -75,7 +75,17 @@ void conf::create_core_config() doc.InsertFirstChild(declaration); auto logger = doc.NewElement("Logger"); - logger->InsertNewChildElement("BaseDir")->SetText("../logs"); + +#ifdef __linux__ + std::string logDir = "/var/log"; +#elif __APPLE__ + std::string home = QStandardPaths::standardLocations(QStandardPaths::HomeLocation)[0].toStdString(); + std::string logDir = std::format("{}/Library/Logs/", home); +#else + std::string logDir = "../logs"; +#endif + + logger->InsertNewChildElement("BaseDir")->SetText(logDir.c_str()); auto* window = doc.NewElement("Window"); window->InsertNewChildElement("Width")->SetText(1700); diff --git a/src/packet/packet.cpp b/src/packet/packet.cpp index 2d0fb8c..780c770 100644 --- a/src/packet/packet.cpp +++ b/src/packet/packet.cpp @@ -1,5 +1,5 @@ #include "packet.h" -#include +#include "utils.h" string Packet::get_link_src() const { @@ -72,9 +72,15 @@ void Packet::set_len(const int len, const int caplen) return time; } -void Packet::set_time(const string& time) +[[nodiscard]] pcap_pkthdr* Packet::get_header() const { - this->time = time; + return header; +} + +void Packet::set_header(pcap_pkthdr* hdr) +{ + this->header = hdr; + this->time = format_timeval_to_string(hdr->ts); } void Packet::set_type_flag(u_short flag) diff --git a/src/packet/packetsource.cpp b/src/packet/packetsource.cpp index 76e3bdc..1ad07ad 100644 --- a/src/packet/packetsource.cpp +++ b/src/packet/packetsource.cpp @@ -7,7 +7,6 @@ #include "dissectors/udp.h" #include "interface.h" #include "utils.h" -#include #include #include <__filesystem/operations.h> #include @@ -17,28 +16,24 @@ using namespace std; PacketSource::~PacketSource() { - free(); - clean_last_dump(); + free_history(); } -void PacketSource::clean_last_dump() +PacketSource::PacketSource() { - if (this->dump_filename.empty()) { - return; - } - - std::filesystem::remove(this->dump_filename); - LOG(INFO) << std::format("remove {}", this->dump_filename); - this->dump_filename = ""; + bridge = std::queue(); + history = std::vector(); + last_access = std::chrono::steady_clock::now(); } -void PacketSource::init(pcap_if_t* device, pcap_t* interface) +void PacketSource::start_on_interface(pcap_if_t* device, pcap_t* interface) { this->device = device; this->interface = interface; this->running = true; - clean_last_dump(); + free_history(); + if (device != nullptr) { dump_filename = std::format("/tmp/{}.{}.pcap", device->name, std::time(0)); } else { @@ -47,9 +42,27 @@ void PacketSource::init(pcap_if_t* device, pcap_t* interface) LOG(INFO) << std::format("open dump handler, filename: {}", dump_filename); this->dump_handler = pcap_dump_open(interface, dump_filename.c_str()); + + fill_thread = std::thread([this]() { + this->capture_packet(); + }); + + consume_thread = std::thread([this]() { + this->consume_queue(); + }); } -void PacketSource::free() +size_t PacketSource::packet_count() const +{ + return history.size(); +} + +Packet* PacketSource::peek(int index) const +{ + return history[index]; +} + +void PacketSource::free_wait() { this->running = false; @@ -66,7 +79,55 @@ void PacketSource::free() pcap_dump_close(this->dump_handler); this->dump_handler = nullptr; } + + if (fill_thread.joinable()) { + fill_thread.join(); + } + + if (consume_thread.joinable()) { + consume_thread.join(); + } } + +void PacketSource::consume_queue() +{ + while (running) { + auto now = std::chrono::steady_clock::now(); + + // 获取锁 + std::unique_lock l(mtx); + + // 桥中没有数据 || 数据量小于平均数 + if (const size_t size = bridge.size(); size == 0 || size < period_average) { + continue; + } + + // 数据不够,并且也不到指定的更新周期 + if (now - last_access < std::chrono::milliseconds(DEFAULT_QUEUE_UPDATE_TIMEOUT_MS)) { + continue; + } + + // 复制队列的全部值 + std::queue q_copy = std::move(bridge); + // 释放队列内存,可能不需要,因为 WAIT_QUEUE_MS 周期内不一定能占用多大内存 + std::queue().swap(bridge); + l.unlock(); + + last_access = now; + period_average = AVERAGE_PERIOD(q_copy.size() / DEFAULT_QUEUE_UPDATE_TIMEOUT_MS); + + // 锁已经被自动释放了 + // 此时消费复制的值,将不会持有锁 + for (; !q_copy.empty(); q_copy.pop()) { + auto p = q_copy.front(); + history.push_back(p); + + emit this->captured(history.size() - 1, p); + dump_flush(p->get_header(), p->get_payload()); + } + } +} + string PacketSource::get_dump_filename() const { return dump_filename; @@ -87,7 +148,37 @@ pcap_t* PacketSource::get_interface() const return interface; } -void PacketSource::run() +/** + * 清空队列与历史捕获包 + */ +void PacketSource::free_history() +{ + // 获取锁 + std::unique_lock l(mtx); + + // 清空历史捕获的包和占用的内存 + ranges::for_each(history, [](Packet* p) { + delete p; + }); + + for (; !bridge.empty(); bridge.pop()) { + delete bridge.front(); + } + + vector().swap(history); + std::queue().swap(bridge); + + // 清空捕获文件的名称 + if (this->dump_filename.empty()) { + return; + } + + std::filesystem::remove(this->dump_filename); + LOG(INFO) << std::format("remove {}", this->dump_filename); + this->dump_filename = ""; +} + +void PacketSource::capture_packet() { const string name = this->device ? this->device->name : this->filename; @@ -113,16 +204,14 @@ void PacketSource::run() continue; } - p->set_time(format_timeval_to_string(pkt_header->ts)); + p->set_header(pkt_header); p->set_payload(&pkt_data); - packetsPtr->push_back(p); - dump_flush(pkt_header, pkt_data); - // 如果并发,会有线程安全问题,size 不准 - emit this->captured(packetsPtr->size() - 1, p); + std::unique_lock l(mtx); + bridge.push(p); } - emit listen_started({ .dump_filename = dump_filename, + emit listen_stopped({ .dump_filename = dump_filename, .interface_name = name, .state = "off" }); } diff --git a/src/window/mainwindow.cpp b/src/window/mainwindow.cpp index 06a6f3d..66191cd 100644 --- a/src/window/mainwindow.cpp +++ b/src/window/mainwindow.cpp @@ -26,41 +26,13 @@ using namespace std; #define HEX_TABLE_FONT_SIZE 11 #define HEX_TABLE_SIDE_LENGTH 18 // 最小尺寸 19 * 22 -void MainWindow::closeEvent(QCloseEvent* event) -{ - event->ignore(); - - hide(); -} -bool MainWindow::event(QEvent* event) -{ - if (event->type() == QEvent::Move) { - auto winConf = conf::instance().core()->FirstChildElement("Window"); - winConf->FirstChildElement("PosX")->SetText(this->geometry().x()); - winConf->FirstChildElement("PosY")->SetText(this->geometry().y()); - - conf::instance().update_core(); - } - - if (event->type() == QEvent::Resize) { - auto winConf = conf::instance().core()->FirstChildElement("Window"); - winConf->FirstChildElement("Width")->SetText(this->geometry().size().width()); - winConf->FirstChildElement("Height")->SetText(this->geometry().size().height()); - - conf::instance().update_core(); - } - - return QMainWindow::event(event); -} - MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ui->setupUi(this); - packets = vector(); - packetSource = new PacketSource(this, &packets); + packetSource = new PacketSource(); statsWindow = new StatsWindow(); statsWindow->packetSource = packetSource; statsWindow->initGraph(); @@ -72,24 +44,10 @@ MainWindow::MainWindow(QWidget* parent) initSlots(); } -void MainWindow::initWindow() -{ - - QSystemTrayIcon* tray = new QSystemTrayIcon(); - tray->setToolTip("WireDolphin"); - - QIcon icon; - icon.addPixmap(QWidget().style()->standardIcon(QStyle::SP_DriveNetIcon).pixmap(QSize(16, 16))); - tray->setIcon(icon); - - trayIcon = new TrayIcon(tray, statsWindow); -} - MainWindow::~MainWindow() { - packetSource->free(); + packetSource->free_wait(); pcap_freealldevs(allDevs); - freePackets(); delete ui; delete interfaceStatusLabel; @@ -114,31 +72,54 @@ MainWindow::~MainWindow() delete aboutQtAct; } -void MainWindow::changeInterfaceIndex(int index) const +void MainWindow::closeEvent(QCloseEvent* event) { - ui->startBtn->setDisabled(index == 0); - ui->loadFileBtn->setDisabled(index != 0); + event->ignore(); + + hide(); } -void MainWindow::freePackets() +bool MainWindow::event(QEvent* event) { - // 使用智能指针可以避免手动 delete 过程 - // 但是现在不知道智能指针的原理,暂时先手动释放 - // auto packets = vector>(); - // auto x = std::make_shared(); - for_each(packets.begin(), packets.end(), [](Packet* p) { - delete p; - }); + if (event->type() == QEvent::Move) { + auto winConf = conf::instance().core()->FirstChildElement("Window"); + winConf->FirstChildElement("PosX")->SetText(this->geometry().x()); + winConf->FirstChildElement("PosY")->SetText(this->geometry().y()); + + conf::instance().update_core(); + } + + if (event->type() == QEvent::Resize) { + auto winConf = conf::instance().core()->FirstChildElement("Window"); + winConf->FirstChildElement("Width")->SetText(this->geometry().size().width()); + winConf->FirstChildElement("Height")->SetText(this->geometry().size().height()); + + conf::instance().update_core(); + } - // 回收内存 - packets.clear(); - vector().swap(packets); + return QMainWindow::event(event); } -void MainWindow::captureInterfaceStarted(packetsource_state state) +void MainWindow::initWindow() { - freePackets(); + QSystemTrayIcon* tray = new QSystemTrayIcon(); + tray->setToolTip("WireDolphin"); + QIcon icon; + icon.addPixmap(QWidget().style()->standardIcon(QStyle::SP_DriveNetIcon).pixmap(QSize(16, 16))); + tray->setIcon(icon); + + trayIcon = new TrayIcon(tray, statsWindow); +} + +void MainWindow::changeInterfaceIndex(int index) const +{ + ui->startBtn->setDisabled(index == 0); + ui->loadFileBtn->setDisabled(index != 0); +} + +void MainWindow::captureInterfaceStarted(packetsource_state state) +{ if (!state.dump_filename.empty()) { dumpFilename->setText(state.dump_filename.c_str()); } @@ -192,16 +173,14 @@ void MainWindow::toggleStartBtn() return; } - packetSource->init(device, interface); - packetSource->start(); + packetSource->start_on_interface(device, interface); return; } - print_stat_info(packetSource->get_interface(), packets.size(), time_start); + print_stat_info(packetSource->get_interface(), packetSource->packet_count(), time_start); - packetSource->free(); - packetSource->wait(); + packetSource->free_wait(); } void MainWindow::initWidgets() @@ -285,19 +264,17 @@ void MainWindow::initWidgets() void MainWindow::updateCaptureStatusLabel() const { - if (packets.empty()) { + size_t count = packetSource->packet_count(); + if (count == 0) { captureStatusLabel->setText(""); return; } - size_t size = packets.size(); - captureStatusLabel->setText("packets: " + QString::number(size) + "/" + QString::number(packets.size())); + captureStatusLabel->setText("packets: " + QString::number(count) + "/" + QString::number(count)); } -void MainWindow::acceptPacket(const int index, Packet*) const +void MainWindow::acceptPacket(const int row, Packet* packet) const { - auto packet = packets[index]; - string src = packet->get_host_src(); string dst = packet->get_host_dst(); if (packet->get_port_src() != 0) { @@ -305,10 +282,10 @@ void MainWindow::acceptPacket(const int index, Packet*) const dst = dst.append(":").append(to_string(packet->get_port_dst())); } - ui->packetsTable->insertRow(index); - ui->packetsTable->setRowHeight(index, 18); + ui->packetsTable->insertRow(row); + ui->packetsTable->setRowHeight(row, 18); - auto* item0 = new QTableWidgetItem(QString::number(index)); + auto* item0 = new QTableWidgetItem(QString::number(row)); auto* item1 = new QTableWidgetItem(packet->get_time().substr(11).c_str()); auto* item2 = new QTableWidgetItem(src.c_str()); auto* item3 = new QTableWidgetItem(dst.c_str()); @@ -326,13 +303,13 @@ void MainWindow::acceptPacket(const int index, Packet*) const item5->setBackground(QBrush(color)); item6->setBackground(QBrush(color)); - ui->packetsTable->setItem(index, 0, item0); - ui->packetsTable->setItem(index, 1, item1); - ui->packetsTable->setItem(index, 2, item2); - ui->packetsTable->setItem(index, 3, item3); - ui->packetsTable->setItem(index, 4, item4); - ui->packetsTable->setItem(index, 5, item5); - ui->packetsTable->setItem(index, 6, item6); + ui->packetsTable->setItem(row, 0, item0); + ui->packetsTable->setItem(row, 1, item1); + ui->packetsTable->setItem(row, 2, item2); + ui->packetsTable->setItem(row, 3, item3); + ui->packetsTable->setItem(row, 4, item4); + ui->packetsTable->setItem(row, 5, item5); + ui->packetsTable->setItem(row, 6, item6); // 滚动到最底部,目前会导致表格卡顿和程序崩溃 // ui->packetsTable->verticalScrollBar()->setValue(ui->packetsTable->verticalScrollBar()->maximum()); @@ -370,13 +347,12 @@ void MainWindow::loadOfflineFile() const } packetSource->set_filename(filename.toStdString()); - packetSource->init(nullptr, interface); - packetSource->start(); + packetSource->start_on_interface(nullptr, interface); } void MainWindow::tableItemClicked(const QModelIndex& index) { - auto packet = packets[index.row()]; + auto packet = packetSource->peek(index.row()); delete frame; delete datalinkTree; From 8f63eec18df856fdfec3bd3311a22f3df06cb4fb Mon Sep 17 00:00:00 2001 From: alex Date: Fri, 23 Aug 2024 23:18:55 +0800 Subject: [PATCH 3/4] feat: supported icmp protocol --- CMakeLists.txt | 2 ++ include/dissectors/icmp.h | 30 ++++++++++++++++++++ src/dissectors/icmp.cpp | 1 + src/packet/packet.cpp | 4 +++ src/packet/packetsource.cpp | 56 ++++++++++++++++++++++++++++++++++--- 5 files changed, 89 insertions(+), 4 deletions(-) create mode 100644 include/dissectors/icmp.h create mode 100644 src/dissectors/icmp.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 79d8b2b..7117a22 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,6 +51,8 @@ add_executable(${PROJECT_NAME} src/trayicon.cpp include/conf.h src/conf.cpp + include/dissectors/icmp.h + src/dissectors/icmp.cpp ) if (CMAKE_BUILD_TYPE STREQUAL "Debug") diff --git a/include/dissectors/icmp.h b/include/dissectors/icmp.h new file mode 100644 index 0000000..13019f5 --- /dev/null +++ b/include/dissectors/icmp.h @@ -0,0 +1,30 @@ +#pragma once + +#define ICMP_TYPE_ECHO_REQUEST 8 +#define ICMP_TYPE_ECHO_REPLY 0 +#define ICMP_TYPE_UNREACHABLE 3 +#define ICMP_TYPE_SOURCE_CLOSED 4 +#include + +using namespace std; + +/** + * +0------7-------15---------------31 + * | Type | Code | Checksum | + * +--------------------------------+ + * | Message Body | + * | (Variable length) | + * +--------------------------------+ + */ +typedef struct icmp_header { + u_char type; + u_char code; +} ICMP_HEADER; + +// Echo Request/Reply +typedef struct icmp_echo { + icmp_header icmp_header; + u_char identifier[2]; + u_char seq_number[2]; + // char data[1500]; // 数据不定长,但不会超过MTU +} ICMP_ECHO; diff --git a/src/dissectors/icmp.cpp b/src/dissectors/icmp.cpp new file mode 100644 index 0000000..a97a206 --- /dev/null +++ b/src/dissectors/icmp.cpp @@ -0,0 +1 @@ +#include "dissectors/icmp.h" diff --git a/src/packet/packet.cpp b/src/packet/packet.cpp index 780c770..45d9dda 100644 --- a/src/packet/packet.cpp +++ b/src/packet/packet.cpp @@ -200,6 +200,10 @@ vector Packet::get_color() const return { 0, 250, 154 }; } + if (this->type == "ICMP") { + return { 211, 211, 211 }; + } + if (this->flags != nullptr) { /** * 三次握手 diff --git a/src/packet/packetsource.cpp b/src/packet/packetsource.cpp index 1ad07ad..030a272 100644 --- a/src/packet/packetsource.cpp +++ b/src/packet/packetsource.cpp @@ -1,6 +1,7 @@ #include "packetsource.h" #include "dissectors/arp.h" #include "dissectors/ethernet.h" +#include "dissectors/icmp.h" #include "dissectors/ipv4.h" #include "dissectors/ipv6.h" #include "dissectors/tcp.h" @@ -229,6 +230,7 @@ void PacketSource::dump_flush(const pcap_pkthdr* h, const u_char* sp) const int PacketSource::parse_header(const u_char** pkt_data, Packet*& p) { ethernet_header* eth = (ETHERNET_HEADER*)*pkt_data; + string info = p->get_info(); p->set_link_src(bytes_to_ascii(eth->link_src, 6, ":")); p->set_link_dst(bytes_to_ascii(eth->link_dst, 6, ":")); @@ -256,9 +258,58 @@ int PacketSource::parse_header(const u_char** pkt_data, Packet*& p) case 0: p->set_type("-"); break; - case 1: + case 1: { p->set_type("ICMP"); + + icmp_header icmp; + memcpy(&icmp, *pkt_data + sizeof(ethernet_header) + p->get_ip_header_len(), sizeof(ICMP_HEADER)); + + switch (icmp.type) { + case ICMP_TYPE_UNREACHABLE: { + switch (icmp.code) { + case 0: + info.append("Network Unreachable"); + break; + case 1: + info.append("Host Unreachable"); + break; + case 2: + info.append("Protocol Unreachable"); + break; + case 3: + info.append("Port Unreachable"); + break; + default: + info.append("Unreachable"); + } + + break; + } + case ICMP_TYPE_SOURCE_CLOSED: + info.append("Request timeout"); + break; + case ICMP_TYPE_ECHO_REQUEST: + case ICMP_TYPE_ECHO_REPLY: + if (icmp.type == ICMP_TYPE_ECHO_REQUEST) { + info.append("Echo Request"); + } else { + info.append("Echo Reply"); + } + + icmp_echo echo; + memcpy(&echo, *pkt_data + sizeof(ethernet_header) + p->get_ip_header_len(), sizeof(ICMP_ECHO)); + + info.append(std::format(" id 0x{}", bytes_to_ascii(echo.identifier, 2, ""))); + info.append(std::format(" seq 0x{}", bytes_to_ascii(echo.seq_number, 2, ""))); + break; + default: + info.append(std::format("type {} code {}", byte_to_ascii(icmp.type), byte_to_ascii(icmp.code))); + break; + } + + p->set_info(info); break; + } case 2: p->set_type("IGMP"); break; @@ -283,8 +334,6 @@ int PacketSource::parse_header(const u_char** pkt_data, Packet*& p) p->set_tcp_flags(flags); p->set_tcp(tcp); - string info = p->get_info(); - int layer4_offset = 14 + p->get_ip_header_len() + p->get_tcp_header_len(); std::istringstream stream(reinterpret_cast(*pkt_data + layer4_offset)); if (string method = is_restful_request(stream); !method.empty()) { @@ -417,7 +466,6 @@ int PacketSource::parse_header(const u_char** pkt_data, Packet*& p) p->set_host_src(bytes_to_ip(arp->sender_host)); p->set_host_dst(bytes_to_ip(arp->destination_host)); - string info = p->get_info(); switch (arp->op) { case 1: info.append("Broadcast Ask "); From 5dbad7a3cb16be6e317bf4d7cef15691d133d198 Mon Sep 17 00:00:00 2001 From: alex Date: Fri, 23 Aug 2024 23:19:42 +0800 Subject: [PATCH 4/4] feat: cycle flush view --- include/mainwindow.h | 2 +- include/packetsource.h | 1 + src/packet/packetsource.cpp | 3 +++ src/window/mainwindow.cpp | 48 ++++++++++++++++++++++--------------- 4 files changed, 34 insertions(+), 20 deletions(-) diff --git a/include/mainwindow.h b/include/mainwindow.h index ce73647..e71168d 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -43,12 +43,12 @@ class MainWindow : public QMainWindow { void tableItemClicked(const QModelIndex& index); void toggleStartBtn(); void initWidgets(); - void updateCaptureStatusLabel() const; void initInterfaceList(); void about(); void activateStatsWindow() const; void saveAsPcap(); void initMenus(); + void updateMajorView(size_t, size_t) const; private: Ui::MainWindow* ui; diff --git a/include/packetsource.h b/include/packetsource.h index dd46849..1fdcf1f 100644 --- a/include/packetsource.h +++ b/include/packetsource.h @@ -91,6 +91,7 @@ class PacketSource final : public QObject { void listen_started(PACKETSOURCE_STATE) const; void listen_stopped(PACKETSOURCE_STATE) const; void captured(size_t, Packet*); + void capture_cycle_flush(size_t, size_t); public: explicit PacketSource(); diff --git a/src/packet/packetsource.cpp b/src/packet/packetsource.cpp index 030a272..94b47c2 100644 --- a/src/packet/packetsource.cpp +++ b/src/packet/packetsource.cpp @@ -126,6 +126,9 @@ void PacketSource::consume_queue() emit this->captured(history.size() - 1, p); dump_flush(p->get_header(), p->get_payload()); } + + // 一个捕获周期结束 + emit this->capture_cycle_flush(period_average, history.size() - 1); } } diff --git a/src/window/mainwindow.cpp b/src/window/mainwindow.cpp index 66191cd..4c30cc1 100644 --- a/src/window/mainwindow.cpp +++ b/src/window/mainwindow.cpp @@ -136,7 +136,6 @@ void MainWindow::captureInterfaceStarted(packetsource_state state) ui->layerTree->clear(); interfaceStatusLabel->setText(state.interface_name.append(": ").append(state.state).c_str()); - updateCaptureStatusLabel(); } void MainWindow::captureInterfaceStopped(packetsource_state state) const @@ -145,8 +144,6 @@ void MainWindow::captureInterfaceStopped(packetsource_state state) const ui->resetBtn->setDisabled(true); ui->startBtn->setText("Start"); interfaceStatusLabel->setText(state.interface_name.append(": ").append(state.state).c_str()); - - updateCaptureStatusLabel(); } void MainWindow::resetCapture() @@ -262,17 +259,6 @@ void MainWindow::initWidgets() } } -void MainWindow::updateCaptureStatusLabel() const -{ - size_t count = packetSource->packet_count(); - if (count == 0) { - captureStatusLabel->setText(""); - return; - } - - captureStatusLabel->setText("packets: " + QString::number(count) + "/" + QString::number(count)); -} - void MainWindow::acceptPacket(const int row, Packet* packet) const { string src = packet->get_host_src(); @@ -310,11 +296,6 @@ void MainWindow::acceptPacket(const int row, Packet* packet) const ui->packetsTable->setItem(row, 4, item4); ui->packetsTable->setItem(row, 5, item5); ui->packetsTable->setItem(row, 6, item6); - - // 滚动到最底部,目前会导致表格卡顿和程序崩溃 - // ui->packetsTable->verticalScrollBar()->setValue(ui->packetsTable->verticalScrollBar()->maximum()); - - updateCaptureStatusLabel(); } void MainWindow::initSlots() @@ -325,6 +306,7 @@ void MainWindow::initSlots() connect(packetSource, &PacketSource::listen_started, this, &MainWindow::captureInterfaceStarted); connect(packetSource, &PacketSource::listen_stopped, this, &MainWindow::captureInterfaceStopped); connect(packetSource, &PacketSource::captured, this, &MainWindow::acceptPacket); + connect(packetSource, &PacketSource::capture_cycle_flush, this, &MainWindow::updateMajorView); connect(ui->packetsTable, &QTableWidget::clicked, this, &MainWindow::tableItemClicked); connect(ui->loadFileBtn, &QPushButton::clicked, this, &MainWindow::loadOfflineFile); } @@ -350,6 +332,34 @@ void MainWindow::loadOfflineFile() const packetSource->start_on_interface(nullptr, interface); } +void MainWindow::updateMajorView(size_t period_average, size_t sum_capture) const +{ + // 滚动到最底部,大流量会导致表格卡顿和程序崩溃 + ui->packetsTable->verticalScrollBar()->setValue(ui->packetsTable->verticalScrollBar()->maximum()); + + size_t count = packetSource->packet_count(); + if (count == 0) { + captureStatusLabel->setText(""); + return; + } + + pcap_stat stats {}; + pcap_stats(packetSource->get_interface(), &stats); + + string msg = std::format("captured: {} droped: {}", count, stats.ps_drop); + captureStatusLabel->setText(msg.c_str()); + + // 悬浮气泡 + std::ostringstream tip; + auto duration = std::chrono::high_resolution_clock::now() - time_start; + tip << count << " captured in " << std::chrono::duration_cast(duration).count() << " seconds" << std::endl; + tip << stats.ps_recv << " received by filter" << std::endl; + tip << stats.ps_ifdrop << " dropped by interface" << std::endl; + tip << stats.ps_drop << " dropped by kernel" << std::endl; + + captureStatusLabel->setToolTip(tip.str().c_str()); +} + void MainWindow::tableItemClicked(const QModelIndex& index) { auto packet = packetSource->peek(index.row());