fix bugs, data races, add: install bash script, just cfg, CMakePresets

This commit is contained in:
deniskhud 2026-02-25 19:40:52 +01:00
parent d67499febf
commit 1ade9d6dc2
19 changed files with 776 additions and 789 deletions

View File

@ -0,0 +1,22 @@
{
"version": 6,
"configurePresets": [
{
"name": "debug",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build/debug",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
}
},
{
"name": "release",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build/release",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release"
}
}
]
}

View File

@ -0,0 +1,30 @@
set shell := ["bash", "-uc"]
default:
@just --list --unsorted
build:
cmake -B build/debug -G Ninja -DCMAKE_BUILD_TYPE=Debug -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
cmake --build build/debug
run *ARGS:
sudo ./build/release/network-traffic-analyzer {{ARGS}}
run-debug *ARGS:
sudo ./build/debug/network-traffic-analyzer {{ARGS}}
capture interface="eth0":
sudo ./build/release/network-traffic-analyzer -i {{interface}}
interfaces:
sudo ./build/release/network-traffic-analyzer --interfaces
lint:
@sed -i 's/-fdeps-format=p1689r5//g; s/-fmodule-mapper=[^ ]*//g; s/-fmodules-ts//g' build/release/compile_commands.json
clang-tidy -p build/release src/**/*.cpp
format:
find src include \( -name '*.cpp' -o -name '*.hpp' \) | xargs clang-format -i
clean:
rm -rf build/

View File

@ -26,6 +26,10 @@ Or grant capabilities:
sudo setcap cap_net_raw,cap_net_admin=eip ./network-traffic-analyzer sudo setcap cap_net_raw,cap_net_admin=eip ./network-traffic-analyzer
``` ```
Or you can use `just` command
```
just run
```
--- ---
# Features # Features
@ -59,29 +63,28 @@ sudo setcap cap_net_raw,cap_net_admin=eip ./network-traffic-analyzer
- FTXUI - FTXUI
- CMake - CMake
# Build # Setup
``` ## 1. clone the repo then
mkdir build && cd build ```bash
cmake .. cd network-traffic-analyzer
make ./install.sh
``` ```
# Usage Example # Usage Example
### Live capture on eth0 ### Live capture on eth0
``` ```
sudo ./network-traffic-analyzer -i eth0 just capture -i eth0
``` ```
### Capture 100 packets ### Capture 100 packets
``` ```
sudo ./network-traffic-analyzer -i wlan0 -c 100 just run -i wlan0 -c 100
``` ```
### Analyze offline pcap file ### Analyze offline pcap file
``` ```
sudo ./network-traffic-analyzer --offline traffic.pcap just run --offline traffic.pcap
``` ```
### Export results (json / csv) ### Export results (json / csv)
``` ```
sudo ./network-traffic-analyzer --json result.json --csv result.csv just run --json result.json --csv result.csv
``` ```

View File

@ -4,37 +4,22 @@
#include <ftxui/dom/elements.hpp> #include <ftxui/dom/elements.hpp>
class View { class View {
public: public:
ftxui::Element render( ftxui::Element render(const StatsSnapshot &data, const std::string &interface, const std::string &filter,
const StatsSnapshot& data, bool capture_finished, std::chrono::seconds timer);
const std::string& interface,
const std::string& filter,
bool capture_finished,
std::chrono::seconds timer
);
private: private:
ftxui::Element render_header( ftxui::Element render_header(const StatsSnapshot &data, const std::string &interface, const std::string &filter);
const StatsSnapshot& data, ftxui::Element render_stats(const StatsSnapshot &data);
const std::string& interface,
const std::string& filter
);
ftxui::Element render_stats(const StatsSnapshot& data);
ftxui::Element render_transport(const StatsSnapshot& data); ftxui::Element render_transport(const StatsSnapshot &data);
ftxui::Element render_application(const StatsSnapshot& data); ftxui::Element render_application(const StatsSnapshot &data);
ftxui::Element render_ip(const StatsSnapshot& data); ftxui::Element render_ip(const StatsSnapshot &data);
ftxui::Element render_pairs(const StatsSnapshot& data); ftxui::Element render_pairs(const StatsSnapshot &data);
ftxui::Element render_bandwidth(const StatsSnapshot& data); ftxui::Element render_bandwidth(const StatsSnapshot &data);
ftxui::Element render_packets(const StatsSnapshot& data); ftxui::Element render_packets(const StatsSnapshot &data);
ftxui::Element render_footer( ftxui::Element render_footer(bool capture_finished, std::chrono::seconds timer);
bool capture_finished,
std::chrono::seconds timer
);
}; };
#endif // VIEW_HPP
#endif //VIEW_HPP

View File

@ -1,28 +1,25 @@
#ifndef PCAPCAPTURE_HPP #ifndef PCAPCAPTURE_HPP
#define PCAPCAPTURE_HPP #define PCAPCAPTURE_HPP
#include <memory>
#include <queue>
#include <pcap/pcap.h>
#include <thread>
#include <deque> #include <deque>
#include <memory>
#include <pcap/pcap.h>
#include <queue>
#include <thread>
//include C libraries
extern "C" {
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/if_ether.h> #include <netinet/if_ether.h>
#include <netinet/tcp.h>
#include <netinet/igmp.h> #include <netinet/igmp.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#define SNAP_LEN 1518 #define SNAP_LEN 1518
}
#include "../packet/IP.hpp"
#include "../../include/stats/protocolStats.hpp" #include "../../include/stats/protocolStats.hpp"
#include "../packet/IP.hpp"
#include "../packet/packet.hpp" #include "../packet/packet.hpp"
/** /**
@ -43,7 +40,7 @@ extern "C" {
*/ */
class PcapCapture { class PcapCapture {
private: private:
/* libpcap error buffer */ /* libpcap error buffer */
char errbuf[PCAP_ERRBUF_SIZE]; char errbuf[PCAP_ERRBUF_SIZE];
@ -52,7 +49,10 @@ private:
/* compiled filter program (expression) */ /* compiled filter program (expression) */
struct bpf_program fp = {}; struct bpf_program fp = {};
/* Active pcap handle */ /* Active pcap handle */
pcap_t *handle = nullptr; std::unique_ptr<pcap_t, decltype(&pcap_close)> handle{nullptr, &pcap_close};
void datalink_type(int type);
uint16_t offset = 0;
std::function<uint16_t(const u_char *)> get_ether_type;
/* Network mask and IP */ /* Network mask and IP */
bpf_u_int32 mask = 0; bpf_u_int32 mask = 0;
@ -72,7 +72,7 @@ private:
* Since libpcap expects a C function pointer, * Since libpcap expects a C function pointer,
* we use a static function and forward the call * we use a static function and forward the call
* to the class instance. * to the class instance.
*/ */
static void callback(u_char *args, const struct pcap_pkthdr *header, const u_char *packet); static void callback(u_char *args, const struct pcap_pkthdr *header, const u_char *packet);
// packet processing logic // packet processing logic
void got_packet(const struct pcap_pkthdr *header, const u_char *packet); void got_packet(const struct pcap_pkthdr *header, const u_char *packet);
@ -80,28 +80,23 @@ private:
/* Separate thread used for live capture */ /* Separate thread used for live capture */
std::thread thread; std::thread thread;
std::atomic<bool> running{false}; std::atomic<bool> running{false};
void stop();
Stats *stats;
public: public:
~PcapCapture();
void print_interfaces(); void print_interfaces();
bool isRunning() { bool isRunning() { return running; }
return running; void setRunning(bool running) { this->running = running; }
}
void setRunning(bool running) {
this->running = running;
}
/* Pointer to statistics engine */ /* Pointer to statistics engine */
Stats* stats;
void set_capabilities(std::string& interface, int num_packets, std::string& filter_exp, int packets_limit, Stats* stats); void set_capabilities(const std::string &interface, int num_packets, const std::string &filter_exp,
int packets_limit, Stats *stats);
void initialize(); void initialize();
void start(); void start();
void start_offline(std::string fpath); void start_offline(const std::string &fpath);
void stop();
}; };
#endif // PCAPCAPTURE_HPP
#endif //PCAPCAPTURE_HPP

View File

@ -8,7 +8,7 @@ struct argsParser {
po::variables_map vm; po::variables_map vm;
void print_help() const; void print_help() const;
argsParser(int argc, char** argv); argsParser(int argc, char **argv);
}; };
#endif //ARGSPARSE_HPP #endif // ARGSPARSE_HPP

View File

@ -3,24 +3,14 @@
#include <string> #include <string>
#include <vector> #include <vector>
enum filter_type { enum filter_type { PROTOCOL, PORT, IP_TYPE, IP_SRC, IP_DEST, NONE };
PROTOCOL,
PORT,
IP_TYPE,
IP_SRC,
IP_DEST,
NONE
};
struct filter { struct filter {
filter_type type; filter_type type;
std::string val; std::string val;
}; };
filter parse(const std::string &str);
std::string get_bpf_filter(const std::vector<filter> &f);
filter parse(std::string str); #endif // FILTER_HPP
std::string get_bpf_filter(std::vector<filter>& f);
#endif //FILTER_HPP

View File

@ -1,72 +1,75 @@
#ifndef IP_HPP #ifndef IP_HPP
#define IP_HPP #define IP_HPP
#include <string>
#include <netinet/ip.h>
#include <netinet/igmp.h>
#include <netinet/ip6.h>
#include "packet.hpp" #include "packet.hpp"
#include <netinet/igmp.h>
#include <netinet/ip.h>
#include <netinet/ip6.h>
#include <string>
/* virtual class for our IPv4, IPv6 classes */ /* virtual class for our IPv4, IPv6 classes */
class IP_class { class IP_class {
protected: protected:
virtual void handle_tcp() = 0; virtual void handle_tcp() = 0;
virtual void handle_udp() = 0; virtual void handle_udp() = 0;
virtual void handle_icmp() = 0; virtual void handle_icmp() = 0;
virtual void handle_icmpv6() = 0; virtual void handle_icmpv6() = 0;
virtual void handle_igmp() = 0; virtual void handle_igmp() = 0;
uint16_t payload_len = 0; uint16_t payload_len = 0;
public: TransportProtocol protocol = TransportProtocol::UNKNOWN;
//getters std::string src;
virtual std::string get_source() = 0; std::string dst;
virtual std::string get_dest() = 0;
public:
std::string get_source();
std::string get_dest();
// getters
/*virtual std::string get_source() = 0;
virtual std::string get_dest() = 0;*/
virtual uint16_t get_src_port() = 0; virtual uint16_t get_src_port() = 0;
virtual uint16_t get_dest_port() = 0; virtual uint16_t get_dest_port() = 0;
virtual TransportProtocol get_protocol() = 0;
uint16_t get_payload_len(); TransportProtocol get_protocol() const;
uint16_t get_payload_len() const;
const uint8_t* payload_ptr = nullptr; const uint8_t *payload_ptr = nullptr;
const uint8_t* get_payload_ptr() const { return payload_ptr; } const uint8_t *get_payload_ptr() const { return payload_ptr; }
virtual ~IP_class() = default; virtual ~IP_class() = default;
}; };
/*** ip4 ***/ /*** ip4 ***/
class IPv4 : public IP_class { class IPv4 : public IP_class {
private: private:
const ip* ip_hdr = nullptr; const ip *ip_hdr = nullptr;
int ip_hdr_len = 0; int ip_hdr_len = 0;
uint16_t src_port = 0; uint16_t src_port = 0;
uint16_t dest_port = 0; uint16_t dest_port = 0;
protected:
protected:
void handle_tcp() override; void handle_tcp() override;
void handle_udp() override; void handle_udp() override;
void handle_icmp() override; void handle_icmp() override;
void handle_icmpv6() override; void handle_icmpv6() override;
void handle_igmp() override; void handle_igmp() override;
public: public:
TransportProtocol get_protocol() override;
std::string get_source() override;
std::string get_dest() override;
uint16_t get_src_port() override; uint16_t get_src_port() override;
uint16_t get_dest_port() override; uint16_t get_dest_port() override;
explicit IPv4(const u_char* data); explicit IPv4(const u_char *data);
}; };
/*** ipv6 ***/ /*** ipv6 ***/
class IPv6 : public IP_class{ class IPv6 : public IP_class {
protected: protected:
void handle_tcp() override; void handle_tcp() override;
void handle_udp() override; void handle_udp() override;
void handle_icmp() override; void handle_icmp() override;
void handle_icmpv6() override; void handle_icmpv6() override;
void handle_igmp() override; void handle_igmp() override;
private:
const ip6_hdr* ip_hdr = nullptr; private:
const ip6_hdr *ip_hdr = nullptr;
int ip_hdr_len = 40; int ip_hdr_len = 40;
in6_addr ip_source; in6_addr ip_source;
@ -75,17 +78,13 @@ private:
uint16_t src_port; uint16_t src_port;
uint16_t dest_port; uint16_t dest_port;
const uint8_t* ptr = nullptr; const uint8_t *ptr = nullptr;
public: public:
explicit IPv6(const u_char* data); explicit IPv6(const u_char *data);
TransportProtocol get_protocol() override;
std::string get_source() override;
std::string get_dest() override;
uint16_t get_src_port() override; uint16_t get_src_port() override;
uint16_t get_dest_port() override; uint16_t get_dest_port() override;
}; };
#endif //IP_HPP #endif // IP_HPP

View File

@ -1,10 +1,11 @@
#ifndef PACKET_HPP #ifndef PACKET_HPP
#define PACKET_HPP #define PACKET_HPP
#include <cstdint> #include <cstdint>
#include <utility>
#include <string> #include <string>
#include <utility>
enum IPVersion { enum IPVersion {
v4, v6, v4,
v6,
}; };
enum class TransportProtocol { enum class TransportProtocol {
@ -32,9 +33,9 @@ struct Packet {
IPVersion ip_version; IPVersion ip_version;
TransportProtocol transport_protocol; TransportProtocol transport_protocol;
ApplicationProtocol application_protocol; ApplicationProtocol application_protocol;
//src address // src address
std::string src; std::string src;
//dest address // dest address
std::string dst; std::string dst;
uint16_t src_port; uint16_t src_port;
uint16_t dst_port; uint16_t dst_port;
@ -42,18 +43,17 @@ struct Packet {
uint32_t total_len; uint32_t total_len;
uint16_t payload_len; uint16_t payload_len;
const uint8_t* payload_ptr; const uint8_t *payload_ptr;
Packet(IPVersion version, TransportProtocol protocol, std::string src, std::string dst, Packet(IPVersion version, TransportProtocol protocol, std::string src, std::string dst, uint16_t src_port,
uint16_t src_port, uint16_t dst_port, uint32_t total_len, uint16_t payload, const uint8_t* payload_ptr) : ip_version(version), transport_protocol(protocol), uint16_t dst_port, uint32_t total_len, uint16_t payload, const uint8_t *payload_ptr)
src(std::move(src)), dst(std::move(dst)), src_port(src_port), dst_port(dst_port), total_len(total_len), payload_len(payload), : ip_version(version), transport_protocol(protocol), src(std::move(src)), dst(std::move(dst)),
payload_ptr(payload_ptr) src_port(src_port), dst_port(dst_port), total_len(total_len), payload_len(payload), payload_ptr(payload_ptr) {
{ application_protocol = get_application_protocol();
this->application_protocol = get_application_protocol(); this->payload_ptr = nullptr;
} }
Packet() { }
private: private:
ApplicationProtocol get_application_protocol(); ApplicationProtocol get_application_protocol();
}; };
#endif //PACKET_HPP #endif // PACKET_HPP

View File

@ -1,17 +1,14 @@
#ifndef PROTOCOLSTATS_HPP #ifndef PROTOCOLSTATS_HPP
#define PROTOCOLSTATS_HPP #define PROTOCOLSTATS_HPP
#include <chrono>
#include <cstdint>
#include "../packet/packet.hpp" #include "../packet/packet.hpp"
#include <unordered_map> #include "ftxui/dom/elements.hpp"
#include <chrono>
#include <filesystem> #include <filesystem>
#include <fstream>
#include <map> #include <map>
#include <queue> #include <queue>
#include "ftxui/dom/elements.hpp" #include <unordered_map>
using namespace ftxui;
struct protocolStats { struct protocolStats {
uint32_t packets = 0; uint32_t packets = 0;
uint32_t bytes = 0; uint32_t bytes = 0;
@ -43,14 +40,12 @@ struct StatsSnapshot {
std::vector<std::vector<std::string>> packets_rows; std::vector<std::vector<std::string>> packets_rows;
uint32_t total_p = 0, total_b = 0; uint32_t total_p = 0, total_b = 0;
//bandwidth // bandwidth
std::vector<BandwidthPoint> bandwidth_history; std::vector<BandwidthPoint> bandwidth_history;
double bandwidth = 0; double bandwidth = 0;
mutable double max_bandwidth = 0; double max_bandwidth = 0;
}; };
/** /**
* @brief Thread-safe statistics engine. * @brief Thread-safe statistics engine.
* *
@ -64,14 +59,13 @@ struct StatsSnapshot {
* All write operations are protected by mutex. * All write operations are protected by mutex.
*/ */
class Stats { class Stats {
private: private:
std::mutex mtx; std::mutex mtx;
uint32_t last_b = 0; uint32_t last_b = 0;
std::chrono::steady_clock::time_point last_tick; std::chrono::steady_clock::time_point last_tick;
std::unordered_map<TransportProtocol, protocolStats> transport_map; std::unordered_map<TransportProtocol, protocolStats> transport_map;
std::unordered_map<ApplicationProtocol, protocolStats> application_map; std::unordered_map<ApplicationProtocol, protocolStats> application_map;
std::unordered_map<std::string, IPStats> ip_map; std::unordered_map<std::string, IPStats> ip_map;
@ -81,10 +75,10 @@ private:
std::deque<Packet> packets; std::deque<Packet> packets;
int limit_packets = 10; int limit_packets = 10;
StatsSnapshot snapshot;
public: public:
void push(const Packet &p) {
void push(const Packet& p) {
std::lock_guard<std::mutex> lock(mtx); std::lock_guard<std::mutex> lock(mtx);
if (packets.size() > static_cast<long unsigned int>(limit_packets)) { if (packets.size() > static_cast<long unsigned int>(limit_packets)) {
packets.pop_front(); packets.pop_front();
@ -92,16 +86,17 @@ public:
packets.push_back(p); packets.push_back(p);
} }
StatsSnapshot snapshot; StatsSnapshot get_snapshot() {
std::lock_guard<std::mutex> lock(mtx);
return snapshot;
}
void update_bandwidth(); void update_bandwidth();
double smooth_value(size_t i, size_t start); double smooth_value(size_t i, size_t start);
double smooth_bandwidth = 0.0; double smooth_bandwidth = 0.0;
void set_packets_limit(int limit) { void set_packets_limit(int limit) { limit_packets = limit; }
limit_packets = limit;
}
void add_packet(Packet &packet); void add_packet(const Packet &packet);
void update_transport_stats(); void update_transport_stats();
void update_application_stats(); void update_application_stats();
@ -109,12 +104,10 @@ public:
void update_pairs(size_t limit = 10); void update_pairs(size_t limit = 10);
void update_packets(); void update_packets();
void export_csv(const std::string& filename); void export_csv(const std::string &filename);
void export_json(const std::string& filename); void export_json(const std::string &filename);
Stats(); Stats();
}; };
#endif // PROTOCOLSTATS_HPP
#endif //PROTOCOLSTATS_HPP

View File

@ -0,0 +1,50 @@
#!/usr/bin/env bash
set -euo pipefail
install_just() {
if command -v just &>/dev/null; then
echo "==> just already installed, skipping."
return
fi
echo "==> Installing just..."
if command -v apt &>/dev/null; then
sudo apt install -y just || {
curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh | bash -s -- --to /usr/local/bin
}
elif command -v dnf &>/dev/null; then
sudo dnf install -y just
elif command -v pacman &>/dev/null; then
sudo pacman -S --needed just
else
curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh | bash -s -- --to /usr/local/bin
fi
}
install_deps() {
echo "==> Installing build dependencies..."
if command -v apt &>/dev/null; then
sudo apt install -y libboost-program-options-dev libpcap-dev cmake ninja-build g++ clang-tidy clang-format
elif command -v dnf &>/dev/null; then
sudo dnf install -y boost-devel libpcap-devel cmake ninja-build gcc-c++ clang-tools-extra
elif command -v pacman &>/dev/null; then
sudo pacman -S --needed boost libpcap cmake ninja gcc clang
else
echo "Unsupported package manager. Install manually: boost, libpcap, cmake, ninja, g++, clang-tidy, clang-format"
exit 1
fi
echo "==> Dependencies ready."
}
build() {
echo "==> Configuring..."
cmake -B build/release -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
echo "==> Building..."
cmake --build build/release
echo ""
echo "Setup complete! Run 'just' to see available commands."
}
install_just
install_deps
build

View File

@ -1,26 +1,22 @@
#include "include/capture/pcapCapture.hpp" #include "include/capture/pcapCapture.hpp"
#include <iostream>
#include <pcap/pcap.h>
#include <boost/program_options.hpp>
#include "include/cli/filter.hpp" #include "include/cli/filter.hpp"
#include <ftxui/component/screen_interactive.hpp> #include <boost/program_options.hpp>
#include <ftxui/component/component.hpp> #include <ftxui/component/component.hpp>
#include <ftxui/component/component_options.hpp> #include <ftxui/component/component_options.hpp>
#include <ftxui/component/screen_interactive.hpp>
#include <iostream>
#include <pcap/pcap.h>
#include "include/cli/argsParse.hpp"
#include "include/TUI/view.hpp" #include "include/TUI/view.hpp"
#define SNAP_LEN 1518 #include "include/cli/argsParse.hpp"
int main(int argc, char **argv) int main(int argc, char **argv) {
{ /* initialize stats */
Stats stats;
/* initialize capture */ /* initialize capture */
PcapCapture capture; PcapCapture capture;
capture.initialize(); capture.initialize();
/* initialize stats */
Stats stats;
/* initializing the command line parser */ /* initializing the command line parser */
argsParser parser(argc, argv); argsParser parser(argc, argv);
@ -43,8 +39,8 @@ int main(int argc, char **argv)
/* get a filter, use vector for multiple */ /* get a filter, use vector for multiple */
std::vector<filter> filters; std::vector<filter> filters;
if (parser.vm.contains("filter")) { if (parser.vm.contains("filter")) {
auto& f = parser.vm["filter"].as<std::vector<std::string>>(); auto &f = parser.vm["filter"].as<std::vector<std::string>>();
for (auto& x : f) { for (auto &x : f) {
filters.push_back(parse(x)); filters.push_back(parse(x));
filterString += x + " "; filterString += x + " ";
} }
@ -57,97 +53,102 @@ int main(int argc, char **argv)
/* set the flags to capture engine */ /* set the flags to capture engine */
capture.set_capabilities(interface, count, expression, limit, &stats); capture.set_capabilities(interface, count, expression, limit, &stats);
std::atomic<bool> capture_finished = false; std::atomic<bool> capture_finished = false;
std::atomic<bool> ui_running = true;
/* if we capture packets offline, we read the file in full, then print the result */ /* if we capture packets offline, we read the file in full, then print the result */
if (isOffline) { if (isOffline) {
capture.start_offline(parser.vm["offline"].as<std::string>()); capture.start_offline(parser.vm["offline"].as<std::string>());
/* full recalculation of statistics after file processing */ /* full recalculation of statistics after file processing */
stats.update_packets(); stats.update_packets();
stats.update_application_stats(); stats.update_application_stats();
stats.update_transport_stats(); stats.update_transport_stats();
stats.update_ip_stats(10); stats.update_ip_stats(10);
stats.update_pairs(); stats.update_pairs();
stats.update_bandwidth(); stats.update_bandwidth();
} }
/* otherwise start live capture */ /* otherwise start live capture */
else { else {
capture.start(); capture.start();
} }
/* UI */ /* UI */
using namespace ftxui; // Timer
auto screen = ScreenInteractive::Fullscreen();
/* our class for UI */
View view;
/* timer */
std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now(); std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now();
std::chrono::seconds timer{0}; std::atomic<std::chrono::seconds> timer;
auto screen = ftxui::ScreenInteractive::Fullscreen();
auto renderer = Renderer([&] { /* our class for UI */
auto snapshot = stats.snapshot; View view;
return view.render(snapshot, std::mutex render_mtx;
interface, std::mutex event_mtx;
filterString, ftxui::Element current_render = isOffline
capture_finished, ? view.render(stats.get_snapshot(), interface, filterString, true, timer.load())
timer); : ftxui::text("Starting capture...");
});
auto component = CatchEvent(renderer, [&](Event e) { std::mutex screen_mtx;
if (e == Event::Character('q') || e == Event::Escape) {
capture.stop();
screen.Exit();
}
return true;
});
auto component = ftxui::Renderer([&] {
std::lock_guard<std::mutex> lock(render_mtx);
return current_render;
});
std::thread updater; component |= ftxui::CatchEvent([&](ftxui::Event e) {
if (e == ftxui::Event::Character('q') || e == ftxui::Event::Escape) {
ui_running = false;
screen.Exit();
return true;
}
return true;
});
std::thread application_thread;
if (!isOffline) {
application_thread = std::thread([&] {
while (!capture_finished && ui_running) {
if (!isOffline) { auto now = std::chrono::steady_clock::now();
updater = std::thread([&] { timer.store(std::chrono::duration_cast<std::chrono::seconds>(now - begin));
while (capture.isRunning()) {
stats.update_packets(); if (timer.load() >= std::chrono::seconds(time) || !capture.isRunning()) {
stats.update_application_stats(); capture_finished = true;
stats.update_transport_stats(); }
stats.update_ip_stats(10);
stats.update_pairs();
stats.update_bandwidth();
screen.PostEvent(Event::Custom); stats.update_packets();
std::this_thread::sleep_for(std::chrono::milliseconds(300)); stats.update_application_stats();
stats.update_transport_stats();
stats.update_ip_stats(10);
stats.update_pairs();
stats.update_bandwidth();
auto current_time = std::chrono::steady_clock::now(); ftxui::Element new_frame =
timer = std::chrono::duration_cast<std::chrono::seconds>( view.render(stats.get_snapshot(), interface, filterString, capture_finished, timer.load());
current_time - begin); {
std::lock_guard<std::mutex> lock(render_mtx);
current_render = new_frame;
}
if (ui_running) {
screen.PostEvent(ftxui::Event::Custom);
}
if (timer >= std::chrono::seconds(time)) // std::this_thread::sleep_for(std::chrono::milliseconds(500));
break; }
} });
}
capture_finished = true; screen.Loop(component);
screen.PostEvent(Event::Custom);
});
}
screen.Loop(component); ui_running = false;
capture_finished = true;
if (updater.joinable()) if (application_thread.joinable())
updater.join(); application_thread.join();
/* check export flags */ // Export stats if needed
if (parser.vm.contains("csv")) { if (parser.vm.contains("csv"))
stats.export_csv(parser.vm["csv"].as<std::string>()); stats.export_csv(parser.vm["csv"].as<std::string>());
} if (parser.vm.contains("json"))
if (parser.vm.contains("json")) { stats.export_json(parser.vm["json"].as<std::string>());
stats.export_json(parser.vm["json"].as<std::string>());
}
return 0; return 0;
} }

View File

@ -1,53 +1,39 @@
#include "../../include/TUI/view.hpp" #include "../../include/TUI/view.hpp"
#include "ftxui/dom/table.hpp" #include "ftxui/dom/table.hpp"
using namespace ftxui;
ftxui::Element View::render(const StatsSnapshot& data, const std::string& interface, const std::string& filter, ftxui::Element View::render(const StatsSnapshot &data, const std::string &interface, const std::string &filter,
bool capture_finished, std::chrono::seconds timer) { bool capture_finished, std::chrono::seconds timer) {
auto header = render_header(data, interface, filter); auto header = render_header(data, interface, filter);
auto transport_section = auto transport_section = hbox({
hbox({ render_transport(data) | flex,
render_transport(data) | flex, separator(),
separator(), render_application(data) | flex,
render_application(data) | flex, separator(),
separator(), render_pairs(data) | flex,
render_pairs(data) | flex, }) |
}) | border; border;
auto ip_section = auto ip_section = hbox({render_ip(data) | border | size(HEIGHT, LESS_THAN, 10) | frame | vscroll_indicator,
hbox({
render_ip(data)
| border
| size(HEIGHT, LESS_THAN, 10)
| frame
| vscroll_indicator,
render_bandwidth(data) render_bandwidth(data) | border | flex});
| border
| flex
});
auto left_panel = auto left_panel = vbox({
vbox({ transport_section,
transport_section, separator(),
separator(), ip_section,
ip_section, }) |
}) | flex_grow; flex_grow;
auto right_panel = auto right_panel = render_packets(data) | border | size(WIDTH, EQUAL, 100) | frame | vscroll_indicator;
render_packets(data)
| border
| size(WIDTH, EQUAL, 100)
| frame
| vscroll_indicator;
auto body = auto body = hbox({
hbox({ left_panel,
left_panel, separator(),
separator(), right_panel,
right_panel, }) |
}) | flex; flex;
auto footer = render_footer(capture_finished, timer); auto footer = render_footer(capture_finished, timer);
@ -68,41 +54,33 @@ ftxui::Element View::render(const StatsSnapshot& data, const std::string& interf
* - Active filter * - Active filter
* - Traffic summary * - Traffic summary
*/ */
ftxui::Element View::render_header(const StatsSnapshot& data, const std::string& interface, const std::string& filter) { ftxui::Element View::render_header(const StatsSnapshot &data, const std::string &interface, const std::string &filter) {
return hbox({ return hbox({
vbox({ vbox({
text("Network Traffic Analyzer") | bold, text("Network Traffic Analyzer") | bold,
text("Interface: " + interface), text("Interface: " + interface),
text("Filter: " + filter), text("Filter: " + filter),
}) | flex, }) | flex,
separator(), separator(),
render_stats(data) | flex, render_stats(data) | flex,
}) | border; }) |
border;
} }
ftxui::Element View::render_stats(const StatsSnapshot& data) { ftxui::Element View::render_stats(const StatsSnapshot &data) {
return vbox({ return vbox({text("=== Traffic summary ===") | bold, text("Total packets: " + std::to_string(data.total_p)),
text("=== Traffic summary ===") | bold, text(std::format("Total bytes : {:.2f} MB", data.total_b / (1024.0 * 1024.0)))}) |
text("Total packets: " + std::to_string(data.total_p)), flex;
text(std::format(
"Total bytes : {:.2f} MB",
data.total_b / (1024.0 * 1024.0)
))
}) | flex;
} }
ftxui::Element View::render_footer(bool capture_finished, std::chrono::seconds timer) { ftxui::Element View::render_footer(bool capture_finished, std::chrono::seconds timer) {
return Element({ return Element({capture_finished
capture_finished ? text("Capture finished (" + std::format("{}", timer) + "). Press 'q' or Esc to exit.") |
? text("Capture finished (" + std::format("{}", timer) + "). Press 'q' or Esc to exit.") bold | color(Color::Yellow) | center
| bold | color(Color::Yellow) | center : text("time: " + std::format("{}", timer) + ". Press 'q' or Esc to exit.") | center |
: text("time: " + std::format("{}", timer) + ". Press 'q' or Esc to exit.") | center size(HEIGHT, EQUAL, 1)});
| size(HEIGHT, EQUAL, 1)
});
} }
ftxui::Element View::render_transport(const StatsSnapshot& data) { ftxui::Element View::render_transport(const StatsSnapshot &data) {
Table table(data.transport_rows); Table table(data.transport_rows);
table.SelectAll().Border(LIGHT); table.SelectAll().Border(LIGHT);
@ -111,12 +89,9 @@ ftxui::Element View::render_transport(const StatsSnapshot& data) {
table.SelectRow(0).Decorate(bold); table.SelectRow(0).Decorate(bold);
table.SelectRow(0).Border(DOUBLE); table.SelectRow(0).Border(DOUBLE);
return vbox({ return vbox({text("=== Transport protocols === ") | bold, table.Render()}) | flex;
text("=== Transport protocols === ") | bold,
table.Render()
}) | flex;
} }
ftxui::Element View::render_application(const StatsSnapshot& data) { ftxui::Element View::render_application(const StatsSnapshot &data) {
Table table(data.app_rows); Table table(data.app_rows);
table.SelectAll().Border(LIGHT); table.SelectAll().Border(LIGHT);
@ -125,12 +100,9 @@ ftxui::Element View::render_application(const StatsSnapshot& data) {
table.SelectRow(0).Decorate(bold); table.SelectRow(0).Decorate(bold);
table.SelectRow(0).Border(DOUBLE); table.SelectRow(0).Border(DOUBLE);
return vbox({ return vbox({text("=== Application protocols ===") | bold, table.Render()}) | flex;
text("=== Application protocols ===") | bold,
table.Render()
}) | flex;
} }
ftxui::Element View::render_ip(const StatsSnapshot& data) { ftxui::Element View::render_ip(const StatsSnapshot &data) {
Table table(data.rows); Table table(data.rows);
table.SelectAll().Border(LIGHT); table.SelectAll().Border(LIGHT);
@ -140,13 +112,12 @@ ftxui::Element View::render_ip(const StatsSnapshot& data) {
table.SelectRow(0).Decorate(bold); table.SelectRow(0).Decorate(bold);
table.SelectRow(0).Border(DOUBLE); table.SelectRow(0).Border(DOUBLE);
return vbox({ return vbox({text("=== Top IP addresses ===") | bold,
text("=== Top IP addresses ===") | bold,
table.Render() table.Render()}) |
}) | flex; flex;
} }
ftxui::Element View::render_pairs(const StatsSnapshot& data) { ftxui::Element View::render_pairs(const StatsSnapshot &data) {
Table table(data.pairs_rows); Table table(data.pairs_rows);
table.SelectAll().Border(LIGHT); table.SelectAll().Border(LIGHT);
@ -155,10 +126,7 @@ ftxui::Element View::render_pairs(const StatsSnapshot& data) {
table.SelectRow(0).Decorate(bold); table.SelectRow(0).Decorate(bold);
table.SelectRow(0).Border(DOUBLE); table.SelectRow(0).Border(DOUBLE);
return vbox({ return vbox({text("=== Top communication pairs ===") | bold, table.Render()}) | flex;
text("=== Top communication pairs ===") | bold,
table.Render()
}) | flex;
} }
/** /**
* @brief Renders bandwidth graph. * @brief Renders bandwidth graph.
@ -167,8 +135,7 @@ ftxui::Element View::render_pairs(const StatsSnapshot& data) {
* Scales dynamically based on max bandwidth. * Scales dynamically based on max bandwidth.
*/ */
ftxui::Element View::render_bandwidth(const StatsSnapshot& data) { ftxui::Element View::render_bandwidth(const StatsSnapshot &data) {
using namespace ftxui;
GraphFunction fn = [this, data](int width, int height) { GraphFunction fn = [this, data](int width, int height) {
std::vector<int> output(width, 0); std::vector<int> output(width, 0);
@ -179,7 +146,6 @@ ftxui::Element View::render_bandwidth(const StatsSnapshot& data) {
size_t n = data.bandwidth_history.size(); size_t n = data.bandwidth_history.size();
size_t start = n > 50 ? n - 50 : 0; size_t start = n > 50 ? n - 50 : 0;
double max_bw = 1.0; double max_bw = 1.0;
for (size_t i = start; i < n; ++i) for (size_t i = start; i < n; ++i)
max_bw = std::max(max_bw, data.bandwidth_history[i].bytes_per_sec); max_bw = std::max(max_bw, data.bandwidth_history[i].bytes_per_sec);
@ -188,16 +154,13 @@ ftxui::Element View::render_bandwidth(const StatsSnapshot& data) {
double t = (double)x / (width - 1); double t = (double)x / (width - 1);
double idx_f = start + t * (n - start - 1); double idx_f = start + t * (n - start - 1);
size_t i0 = (size_t)idx_f; size_t i0 = (size_t)idx_f;
size_t i1 = std::min(i0 + 1, n - 1); size_t i1 = std::min(i0 + 1, n - 1);
double frac = idx_f - i0; double frac = idx_f - i0;
double bw = double bw = data.bandwidth_history[i0].bytes_per_sec * (1.0 - frac) +
data.bandwidth_history[i0].bytes_per_sec * (1.0 - frac) + data.bandwidth_history[i1].bytes_per_sec * frac;
data.bandwidth_history[i1].bytes_per_sec * frac;
double v = bw / max_bw; double v = bw / max_bw;
output[x] = static_cast<int>(v * (height - 1)); output[x] = static_cast<int>(v * (height - 1));
@ -207,13 +170,10 @@ ftxui::Element View::render_bandwidth(const StatsSnapshot& data) {
}; };
return vbox({ return vbox({
text(std::format(" Bandwidth: {:.2f} KB / max: {:.2f} KB", data.bandwidth, data.max_bandwidth)) | bold, text(std::format(" Bandwidth: {:.2f} KB / max: {:.2f} KB", data.bandwidth, data.max_bandwidth)) | bold,
graph(fn) graph(fn) | size(HEIGHT, EQUAL, 20) | size(WIDTH, EQUAL, 60) | border | color(Color::Green),
| size(HEIGHT, EQUAL, 20) | size(WIDTH, EQUAL, 60) });
| border
| color(Color::Green),
}) ;
} }
ftxui::Element View::render_packets(const StatsSnapshot& data) { ftxui::Element View::render_packets(const StatsSnapshot &data) {
Table table(data.packets_rows); Table table(data.packets_rows);
table.SelectAll().Border(LIGHT); table.SelectAll().Border(LIGHT);
@ -221,9 +181,8 @@ ftxui::Element View::render_packets(const StatsSnapshot& data) {
table.SelectRow(0).SeparatorVertical(LIGHT); table.SelectRow(0).SeparatorVertical(LIGHT);
table.SelectRow(0).Decorate(bold); table.SelectRow(0).Decorate(bold);
table.SelectRow(0).Border(DOUBLE); table.SelectRow(0).Border(DOUBLE);
return vbox({ return vbox({text("=== Packets ===") | bold,
text("=== Packets ===") | bold,
table.Render() | flex table.Render() | flex}) |
}) | flex; flex;
} }

View File

@ -1,102 +1,120 @@
#include "../../include/capture/pcapCapture.hpp" #include "../../include/capture/pcapCapture.hpp"
#include "../../include/stats/protocolStats.hpp" #include "../../include/stats/protocolStats.hpp"
/* get a list of all available network interfaces */ /* get a list of all available network interfaces */
void PcapCapture::initialize() { void PcapCapture::initialize() {
/* find all devs available in network, save them to pcap_if_t struct (interfaces) */ /* find all devs available in network, save them to pcap_if_t struct (interfaces) */
if (pcap_findalldevs(&interfaces, errbuf) == -1) { if (pcap_findalldevs(&interfaces, errbuf) == -1) {
fprintf(stderr, "Error: pcap_findalldevs has been failed - %s\n", errbuf); throw std::runtime_error("Error: pcap_findalldevs has been failed");
/*fprintf(stderr, "Error: pcap_findalldevs has been failed - %s\n", errbuf);*/
} }
} }
void PcapCapture::datalink_type(int type) {
switch (type) {
case DLT_EN10MB: {
offset = 14;
get_ether_type = [](const u_char *p) {
auto *eth = reinterpret_cast<const ether_header *>(p);
return ntohs(eth->ether_type);
};
break;
}
case DLT_LINUX_SLL: {
offset = 16;
get_ether_type = [](const u_char *p) { return ntohs(*reinterpret_cast<const uint16_t *>(p + 14)); };
break;
}
case DLT_LINUX_SLL2: {
offset = 20;
get_ether_type = [](const u_char *p) { return ntohs(*reinterpret_cast<const uint16_t *>(p + 18)); };
break;
}
default:
throw std::runtime_error("Unsupported datalink type");
}
}
/** /**
* Start live packet capture. * Start live packet capture.
* *
* Steps: * Steps:
* 1. Resolve network mask * 1. Resolve network mask
* 2. Open device in promiscuous mode * 2. Open device in promiscuous mode
* 3. Compile and apply BPF filter (if provided) * 3. Compile and apply BPF filter (if provided)
* 4. Start pcap_loop in a separate thread * 4. Start pcap_loop in a separate thread
*/ */
void PcapCapture::start() { void PcapCapture::start() {
// getting the netmask of the interface // getting the netmask of the interface
if (pcap_lookupnet(interface.c_str(), &net, &mask, errbuf) == -1) { if (pcap_lookupnet(interface.c_str(), &net, &mask, errbuf) == -1) {
fprintf(stderr, "Couldn't get netmask for device %s: %s\n", fprintf(stderr, "Couldn't get netmask for device %s: %s\n", interface.c_str(), errbuf);
interface.c_str(), errbuf);
net = 0; net = 0;
mask = 0; mask = 0;
} }
/* open capture device */ /* open capture device */
handle = pcap_open_live(interface.c_str(), SNAP_LEN, 1, 1000, errbuf); handle.reset(pcap_open_live(interface.c_str(), SNAP_LEN, 1, 1000, errbuf));
if (handle == nullptr) { if (handle == nullptr) {
fprintf(stderr, "Couldn't open device %s: %s\n", interface.c_str(), errbuf); throw std::runtime_error("Couldn't open device " + interface + ": " + errbuf);
exit(EXIT_FAILURE);
} }
if (pcap_datalink(handle) != DLT_EN10MB) { datalink_type(pcap_datalink(handle.get()));
fprintf(stderr, "%s is not an Ethernet\n", interface.c_str());
exit(EXIT_FAILURE);
}
if (!filter_exp.empty()) { if (!filter_exp.empty()) {
/* compile the filter expression */ /* compile the filter expression */
if (pcap_compile(handle, &fp, filter_exp.c_str(), 0, net) == -1) { if (pcap_compile(handle.get(), &fp, filter_exp.c_str(), 0, net) == -1) {
fprintf(stderr, "Couldn't parse filter %s: %s\n", throw std::runtime_error("Couldn't parse filter " + filter_exp + ": " + pcap_geterr(handle.get()));
filter_exp.c_str(), pcap_geterr(handle));
exit(EXIT_FAILURE);
} }
/* apply the compiled filter */ /* apply the compiled filter */
if (pcap_setfilter(handle, &fp) == -1) { if (pcap_setfilter(handle.get(), &fp) == -1) {
fprintf(stderr, "Couldn't install filter %s: %s\n", throw std::runtime_error("Couldn't install filter " + filter_exp + ": " + pcap_geterr(handle.get()));
filter_exp.c_str(), pcap_geterr(handle));
exit(EXIT_FAILURE);
} }
pcap_freecode(&fp);
} }
/* start a separate thread */ /* start a separate thread */
running = true; running = true;
thread = std::thread([this]() { thread = std::thread([this]() {
if (pcap_loop(handle, num_packets, &PcapCapture::callback, reinterpret_cast<u_char*>(this)) < 0) { if (pcap_loop(handle.get(), num_packets, &PcapCapture::callback, reinterpret_cast<u_char *>(this)) < 0) {
//fprintf(stderr, "Error in pcap_loop: %s\n", pcap_geterr(handle)); // fprintf(stderr, "Error in pcap_loop: %s\n", pcap_geterr(handle));
//pcap_close(handle); // pcap_close(handle);
// throw std::runtime_error("Couldn't start capture");
} }
running = false; running = false;
}); });
} }
PcapCapture::~PcapCapture() { stop(); }
void PcapCapture::stop() { void PcapCapture::stop() {
if (interfaces) { pcap_freecode(&fp);
pcap_freealldevs(interfaces); if (!handle)
} return;
if (handle) {
if (running == true) pcap_breakloop(handle);
pcap_close(handle);
//handle = nullptr;
}
if (thread.joinable()) { running = false;
pcap_breakloop(handle.get());
if (thread.joinable())
thread.join(); thread.join();
}
/*if (!filter_exp.empty()) {
pcap_freecode(&fp);
}*/
handle.reset();
if (interfaces) {
pcap_freealldevs(interfaces);
interfaces = nullptr;
}
} }
/* print all available interfaces */ /* print all available interfaces */
void PcapCapture::print_interfaces() { void PcapCapture::print_interfaces() {
int i = 0; int i = 0;
for (pcap_if_t *dev = interfaces; dev; dev = dev->next) { for (pcap_if_t *dev = interfaces; dev; dev = dev->next) {
printf("%d. %s ",++i, dev->name); printf("%d. %s ", ++i, dev->name);
if (dev->description) { if (dev->description) {
printf("(%s)\n", dev->description); printf("(%s)\n", dev->description);
} } else {
else {
printf("\n"); printf("\n");
} }
} }
@ -111,13 +129,10 @@ void PcapCapture::print_interfaces() {
* @param header Packet metadata * @param header Packet metadata
* @param packet Raw packet bytes * @param packet Raw packet bytes
*/ */
void PcapCapture::callback( void PcapCapture::callback(u_char *user, const struct pcap_pkthdr *header, const u_char *packet) {
u_char* user, auto *self = reinterpret_cast<PcapCapture *>(user);
const struct pcap_pkthdr* header, if (!self->isRunning())
const u_char* packet return;
) {
auto* self = reinterpret_cast<PcapCapture*>(user);
if (!self->isRunning()) return;
self->got_packet(header, packet); self->got_packet(header, packet);
} }
@ -135,49 +150,41 @@ void PcapCapture::callback(
* Other Ethernet types are ignored. * Other Ethernet types are ignored.
*/ */
void PcapCapture::got_packet(const struct pcap_pkthdr *header, const u_char *packet) { void PcapCapture::got_packet(const struct pcap_pkthdr *header, const u_char *packet) {
if (!running) return; if (!running)
return;
// --- Ethernet header --- // --- Ethernet header ---
const auto* ethernet = reinterpret_cast<const ether_header*>(packet); // const auto* ethernet = reinterpret_cast<const ether_header*>(packet + offset);
uint16_t ether_type = ntohs(ethernet->ether_type); uint16_t ether_type = get_ether_type(packet);
Packet packetView;
/* if we have a ipv4 type */ /* if we have a ipv4 type */
if (ether_type == ETHERTYPE_IP) { if (ether_type == ETHERTYPE_IP) {
IPv4 ip(packet + sizeof(struct ether_header)); IPv4 ip(packet + offset);
TransportProtocol prot = ip.get_protocol(); TransportProtocol prot = ip.get_protocol();
packetView = Packet(v4, prot, ip.get_source(), ip.get_dest(), ip.get_src_port(), ip.get_dest_port(), header->len, ip.get_payload_len(), ip.get_payload_ptr()); Packet packetView(v4, prot, ip.get_source(), ip.get_dest(), ip.get_src_port(), ip.get_dest_port(), header->len,
ip.get_payload_len(), ip.get_payload_ptr());
stats->add_packet(packetView); stats->add_packet(packetView);
stats->push(packetView); stats->push(packetView);
} }
/* ipv6 type */ /* ipv6 type */
if (ether_type == ETHERTYPE_IPV6) { else if (ether_type == ETHERTYPE_IPV6) {
IPv6 ip(packet + sizeof(struct ether_header)); IPv6 ip(packet + offset);
TransportProtocol prot = ip.get_protocol(); TransportProtocol prot = ip.get_protocol();
packetView = Packet(v6, prot, ip.get_source(), ip.get_dest(), ip.get_src_port(), ip.get_dest_port(), header->len, ip.get_payload_len(), ip.get_payload_ptr()); Packet packetView(v6, prot, ip.get_source(), ip.get_dest(), ip.get_src_port(), ip.get_dest_port(), header->len,
ip.get_payload_len(), ip.get_payload_ptr());
stats->add_packet(packetView); stats->add_packet(packetView);
stats->push(packetView); stats->push(packetView);
}
if (ether_type == ETHERTYPE_VLAN) {
ethernet = (ether_header*)(packet + 4);
}
if (ether_type == ETHERTYPE_ARP) {
} }
} }
void PcapCapture::set_capabilities(std::string& interface, int num_packets, std::string& filter_exp, int packets_limit, Stats* stats) { void PcapCapture::set_capabilities(const std::string &interface, int num_packets, const std::string &filter_exp,
const int packets_limit, Stats *stats) {
this->interface = interface; this->interface = interface;
this->num_packets = num_packets; this->num_packets = num_packets;
this->filter_exp = filter_exp; this->filter_exp = filter_exp;
this->stats = stats; this->stats = stats;
this->stats->set_packets_limit(packets_limit); this->stats->set_packets_limit(packets_limit);
} }
/** /**
* @brief Processes packets from an offline .pcap file. * @brief Processes packets from an offline .pcap file.
@ -189,18 +196,17 @@ void PcapCapture::set_capabilities(std::string& interface, int num_packets, std:
* *
* Used for post-capture analysis and exporting results. * Used for post-capture analysis and exporting results.
*/ */
void PcapCapture::start_offline(std::string fpath) { void PcapCapture::start_offline(const std::string &fpath) {
handle = pcap_open_offline(fpath.c_str(), errbuf); handle.reset(pcap_open_offline(fpath.c_str(), errbuf));
if (handle == nullptr) { if (handle == nullptr) {
fprintf(stderr, "Error opening offline file: %s\n", errbuf); fprintf(stderr, "Error opening offline file: %s\n", errbuf);
return; return;
} }
datalink_type(pcap_datalink(handle.get()));
running = true; running = true;
pcap_loop(handle, num_packets, pcap_loop(handle.get(), num_packets, &PcapCapture::callback, reinterpret_cast<u_char *>(this));
&PcapCapture::callback,
reinterpret_cast<u_char*>(this));
running = false; running = false;
} }

View File

@ -1,64 +1,54 @@
#include "../../include/cli/argsParse.hpp" #include "../../include/cli/argsParse.hpp"
#include <iostream> #include <iostream>
argsParser::argsParser(int argc, char** argv) { argsParser::argsParser(int argc, char **argv) {
desc.add_options() desc.add_options()("help,h", "Display this help message and exit")("interfaces, interfaces",
("help,h", "Display this help message and exit") "Display all possible interfaces")(
("interfaces, interfaces","Display all possible interfaces") "interface,i", po::value<std::string>()->default_value("wlan0"),
("interface,i", po::value<std::string>()->default_value("wlan0"), "Network interface to capture packets from (e.g. eth0, wlan0, any)")
"Network interface to capture packets from (e.g. eth0, wlan0, any)")
("count,c", po::value<int>()->default_value(0), ("count,c", po::value<int>()->default_value(0), "Number of packets to capture (0 = unlimited)")(
"Number of packets to capture (0 = unlimited)") "time, t", po::value<int>()->default_value(INT_MAX), "Working time (in seconds)")
("time, t", po::value<int>()->default_value(INT_MAX),"Working time (in seconds)")
("offline,r", po::value<std::string>(), ("offline,r", po::value<std::string>(), "Read packets from an offline pcap file")
"Read packets from an offline pcap file")
("filter,f", po::value<std::vector<std::string>>()->composing(), ("filter,f", po::value<std::vector<std::string>>()->composing(),
"Traffic filter (can be used multiple times)\n" "Traffic filter (can be used multiple times)\n"
" proto:<name> tcp | udp | icmp | dns\n" " proto:<name> tcp | udp | icmp | dns\n"
" src:<ip> Source IP address\n" " src:<ip> Source IP address\n"
" dst:<ip> Destination IP address\n" " dst:<ip> Destination IP address\n"
" port:<number> Source or destination port") " port:<number> Source or destination port")
("sort,s", po::value<std::string>()->default_value("bytes"), ("sort,s", po::value<std::string>()->default_value("bytes"), "Sort field: bytes | packets | ip")
"Sort field: bytes | packets | ip")
("order,o", po::value<std::string>()->default_value("desc"), ("order,o", po::value<std::string>()->default_value("desc"), "Sort order: asc | desc")
"Sort order: asc | desc")
("limit,n", po::value<int>()->default_value(43), ("limit,n", po::value<int>()->default_value(43), "Limit number of displayed entries")
"Limit number of displayed entries")
("csv", po::value<std::string>(), ("csv", po::value<std::string>(), "Export analysis results to CSV file")
"Export analysis results to CSV file")
("json", po::value<std::string>(), ("json", po::value<std::string>(), "Export analysis results to JSON file");
"Export analysis results to JSON file");
po::store(po::parse_command_line(argc, argv, desc), vm); po::store(po::parse_command_line(argc, argv, desc), vm);
po::notify(vm); po::notify(vm);
} }
void argsParser::print_help() const { void argsParser::print_help() const {
std::cout << std::cout << "Network Traffic Analyzer\n"
"Network Traffic Analyzer\n" "========================\n\n"
"========================\n\n" "Usage:\n"
"Usage:\n" " ./network-traffic-analyzer [options]\n\n"
" ./network-traffic-analyzer [options]\n\n" "Description:\n"
"Description:\n" " Captures and analyzes network traffic from live interfaces or\n"
" Captures and analyzes network traffic from live interfaces or\n" " offline pcap files. Provides protocol statistics, top talkers,\n"
" offline pcap files. Provides protocol statistics, top talkers,\n" " and bandwidth usage information.\n\n";
" and bandwidth usage information.\n\n";
std::cout << desc << "\n"; std::cout << desc << "\n";
std::cout << std::cout << "Examples:\n"
"Examples:\n" " ./network-traffic-analyzer -i wlan0 --count 100 --time 10\n"
" ./network-traffic-analyzer -i wlan0 --count 100 --time 10\n" " ./network-traffic-analyzer -i any --filter port:54\n"
" ./network-traffic-analyzer -i any --filter port:54\n" " ./network-traffic-analyzer --offline traffic.pcap --json result.json\n\n";
" ./network-traffic-analyzer --offline traffic.pcap --json result.json\n\n";
std::cout << "To end the program, press 'q' or Esc to exit.\n"; std::cout << "To end the program, press 'q' or Esc to exit.\n";
} }

View File

@ -1,60 +1,81 @@
#include "../../include/cli/filter.hpp" #include "../../include/cli/filter.hpp"
#include <map> #include <map>
#include <stdexcept>
filter parse(std::string str) { filter parse(const std::string &str) {
auto pos = str.find(':'); auto pos = str.find(':');
if (pos == std::string::npos) { if (pos == std::string::npos) {
throw std::invalid_argument("Invalid filter format: '" + str + "' (expected key:value)");
} }
std::string type = str.substr(0, pos); std::string type = str.substr(0, pos);
std::string value = str.substr(pos + 1); std::string value = str.substr(pos + 1);
if (type == "protocol") return {PROTOCOL, value}; if (type == "protocol")
if (type == "port") return {PORT, value}; return {PROTOCOL, value};
if (type == "dest") return {IP_DEST, value}; if (type == "port")
if (type == "src") return {IP_SRC, value}; return {PORT, value};
if (type == "ip") return {IP_TYPE, value}; if (type == "dest")
return {IP_DEST, value};
if (type == "src")
return {IP_SRC, value};
if (type == "ip")
return {IP_TYPE, value};
return {NONE, value}; return {NONE, value};
} }
std::string get_bpf_filter(std::vector<filter>& f) { std::string get_bpf_filter(const std::vector<filter> &f) {
std::map<filter_type, std::vector<std::string>> groups; std::map<filter_type, std::vector<std::string>> groups;
for (const auto& x : f) { for (const auto &x : f) {
switch (x.type) { switch (x.type) {
case PROTOCOL: case PROTOCOL:
if (x.val == "dns") if (x.val == "dns")
groups[PROTOCOL].push_back("port 53"); groups[PROTOCOL].emplace_back("port 53");
else else if (x.val == "http")
groups[PROTOCOL].push_back(x.val); groups[PROTOCOL].emplace_back("port 80");
break; else if (x.val == "https")
groups[PROTOCOL].emplace_back("port 443");
else if (x.val == "ssh")
groups[PROTOCOL].emplace_back("port 22");
else if (x.val == "ftp")
groups[PROTOCOL].emplace_back("port 21");
else if (x.val == "smtp")
groups[PROTOCOL].emplace_back("port 25");
else
groups[PROTOCOL].push_back(x.val);
break;
case IP_DEST: case IP_DEST:
groups[IP_DEST].push_back("dst host " + x.val); groups[IP_DEST].push_back("dst host " + x.val);
break; break;
case IP_SRC: case IP_SRC:
groups[IP_SRC].push_back("src host " + x.val); groups[IP_SRC].push_back("src host " + x.val);
break; break;
case PORT: case PORT:
groups[PORT].push_back("port " + x.val); groups[PORT].push_back("port " + x.val);
break; break;
case IP_TYPE: case IP_TYPE: {
groups[IP_TYPE].push_back(x.val); if (x.val == "v4" || x.val == "4" || x.val == "ipv4")
break; groups[IP_TYPE].emplace_back("ip");
else if (x.val == "v6" || x.val == "6" || x.val == "ipv6")
groups[IP_TYPE].emplace_back("ip6");
else
throw std::invalid_argument("Unknown IP type: '" + x.val + "'");
break;
}
default: default:
break; break;
} }
} }
std::string result; std::string result;
bool first_group = true; bool first_group = true;
for (auto& [type, parts] : groups) { for (auto &[type, parts] : groups) {
if (!first_group) if (!first_group)
result += " and "; result += " and ";
first_group = false; first_group = false;
@ -74,4 +95,3 @@ std::string get_bpf_filter(std::vector<filter>& f) {
return result; return result;
} }

View File

@ -1,211 +1,176 @@
#include "../../include/packet/IP.hpp" #include "../../include/packet/IP.hpp"
#include <cstdio>
#include <arpa/inet.h> #include <arpa/inet.h>
#include <array>
#include <cstdio>
#include <netinet/icmp6.h> #include <netinet/icmp6.h>
#include <netinet/ip_icmp.h> #include <netinet/ip_icmp.h>
#include <netinet/tcp.h> #include <netinet/tcp.h>
#include <netinet/udp.h> #include <netinet/udp.h>
#include <stdexcept>
uint16_t IP_class::get_payload_len() const { return payload_len; }
uint16_t IP_class::get_payload_len() { TransportProtocol IP_class::get_protocol() const { return protocol; }
return payload_len; std::string IP_class::get_source() { return src; }
} std::string IP_class::get_dest() { return dst; }
/*** Ipv4 ***/ /*** Ipv4 ***/
IPv4::IPv4(const u_char* data) { IPv4::IPv4(const u_char *data) {
ip_hdr = reinterpret_cast<const ip*>(data); ip_hdr = reinterpret_cast<const ip *>(data);
src = inet_ntoa(ip_hdr->ip_src);
dst = inet_ntoa(ip_hdr->ip_dst);
ip_hdr_len = ip_hdr->ip_hl * 4; ip_hdr_len = ip_hdr->ip_hl * 4;
if (ip_hdr_len < 20) { if (ip_hdr_len < 20) {
fprintf(stderr, "Failed to initial IPv4 "); throw std::runtime_error("Failed to initial IPv4 ");
} }
}
TransportProtocol IPv4::get_protocol() {
switch (ip_hdr->ip_p) { switch (ip_hdr->ip_p) {
case IPPROTO_TCP: case IPPROTO_TCP:
handle_tcp(); IPv4::handle_tcp();
return TransportProtocol::TCP; break;
case IPPROTO_UDP:
IPv4::handle_udp();
break;
case IPPROTO_UDP: case IPPROTO_ICMP:
handle_udp(); IPv4::handle_icmp();
return TransportProtocol::UDP; break;
case IPPROTO_ICMP: case IPPROTO_ICMPV6:
handle_icmp(); IPv4::handle_icmpv6();
return TransportProtocol::ICMP; break;
case IPPROTO_IGMP:
case IPPROTO_ICMPV6: IPv4::handle_igmp();
handle_icmpv6(); break;
return TransportProtocol::ICMP6; default:
protocol = TransportProtocol::UNKNOWN;
case IPPROTO_IGMP: break;
handle_igmp();
return TransportProtocol::IGMP;
default:
return TransportProtocol::UNKNOWN;
} }
} }
void IPv4::handle_tcp() { void IPv4::handle_tcp() {
auto* tcp = reinterpret_cast<tcphdr*>((u_char*)ip_hdr + ip_hdr_len); const auto *tcp = reinterpret_cast<const tcphdr *>(reinterpret_cast<const u_char *>(ip_hdr) + ip_hdr_len);
src_port = ntohs(tcp->source); src_port = ntohs(tcp->source);
dest_port = ntohs(tcp->dest); dest_port = ntohs(tcp->dest);
payload_ptr = reinterpret_cast<u_char*>(tcp + tcp->doff * 4); payload_ptr = reinterpret_cast<const u_char *>(tcp) + tcp->doff * 4;
payload_len = ntohs(ip_hdr->ip_len) - (ip_hdr_len + tcp->doff * 4); payload_len = ntohs(ip_hdr->ip_len) - (ip_hdr_len + tcp->doff * 4);
protocol = TransportProtocol::TCP;
} }
void IPv4::handle_udp() { void IPv4::handle_udp() {
auto* udp = reinterpret_cast<udphdr*>((u_char*)ip_hdr + ip_hdr_len); const auto *udp = reinterpret_cast<const udphdr *>(reinterpret_cast<const u_char *>(ip_hdr) + ip_hdr_len);
dest_port = ntohs(udp->dest); dest_port = ntohs(udp->dest);
src_port = ntohs(udp->source); src_port = ntohs(udp->source);
payload_ptr = (u_char*)udp + sizeof(udphdr); payload_ptr = reinterpret_cast<const u_char *>(udp) + sizeof(udphdr);
payload_len = ntohs(udp->len) - sizeof(udphdr); payload_len = ntohs(udp->len) - sizeof(udphdr);
}
void IPv4::handle_icmp() {
auto* icmp = reinterpret_cast<icmphdr*>((u_char*)ip_hdr + ip_hdr_len);
protocol = TransportProtocol::UDP;
}
void IPv4::handle_icmp() { protocol = TransportProtocol::ICMP; }
void IPv4::handle_icmpv6() { protocol = TransportProtocol::ICMP6; }
void IPv4::handle_igmp() { protocol = TransportProtocol::IGMP; }
} uint16_t IPv4::get_src_port() { return src_port; }
void IPv4::handle_icmpv6() { uint16_t IPv4::get_dest_port() { return dest_port; }
auto* icmp = reinterpret_cast<icmp6_hdr*>((u_char*)ip_hdr + ip_hdr_len);
}
void IPv4::handle_igmp() {
auto* igmp = reinterpret_cast<struct igmp*>((u_char*)ip_hdr + ip_hdr_len);
}
std::string IPv4::get_source() {
return std::string(inet_ntoa(ip_hdr->ip_src));
}
std::string IPv4::get_dest() {
return std::string(inet_ntoa(ip_hdr->ip_dst));
}
uint16_t IPv4::get_src_port() {
return src_port;
}
uint16_t IPv4::get_dest_port() {
return dest_port;
}
/*** Ipv6 ***/ /*** Ipv6 ***/
IPv6::IPv6(const u_char* data) { IPv6::IPv6(const u_char *data) {
ip_hdr = reinterpret_cast<const ip6_hdr*>(data); ip_hdr = reinterpret_cast<const ip6_hdr *>(data);
}
TransportProtocol IPv6::get_protocol() {
uint8_t hdr = ip_hdr->ip6_nxt; uint8_t hdr = ip_hdr->ip6_nxt;
std::array<char, INET6_ADDRSTRLEN> src{};
inet_ntop(AF_INET6, &ip_hdr->ip6_src, src.data(), sizeof(src));
this->src = src.data();
ptr = reinterpret_cast<const uint8_t*>(ip_hdr + 1); std::array<char, INET6_ADDRSTRLEN> dst{};
inet_ntop(AF_INET6, &ip_hdr->ip6_dst, dst.data(), sizeof(dst));
this->dst = dst.data();
ptr = reinterpret_cast<const uint8_t *>(ip_hdr + 1);
while (true) { while (true) {
switch (hdr) { switch (hdr) {
case IPPROTO_TCP: case IPPROTO_TCP:
handle_tcp(); IPv6::handle_tcp();
return TransportProtocol::TCP; return;
case IPPROTO_UDP:
IPv6::handle_udp();
return;
case IPPROTO_ICMP:
IPv6::handle_icmp();
return;
case IPPROTO_ICMPV6:
IPv6::handle_icmpv6();
return;
case IPPROTO_UDP: case IPPROTO_IGMP:
handle_udp(); IPv6::handle_igmp();
return TransportProtocol::UDP; return;
/* if we have an extension headers, dont leave from loop,
* keep find a protocol type
*/
case IPPROTO_HOPOPTS:
case IPPROTO_ROUTING:
case IPPROTO_DSTOPTS: {
const auto *ext = reinterpret_cast<const ip6_ext *>(ptr);
hdr = ext->ip6e_nxt;
ptr += (ext->ip6e_len + 1) * 8;
break;
}
case IPPROTO_FRAGMENT: {
case IPPROTO_ICMP: const auto *frag = reinterpret_cast<const ip6_frag *>(ptr);
handle_icmp(); hdr = frag->ip6f_nxt;
return TransportProtocol::ICMP; ptr += sizeof(ip6_frag);
break;
case IPPROTO_ICMPV6: }
handle_icmpv6(); default:
return TransportProtocol::ICMP6; protocol = TransportProtocol::UNKNOWN;
return;
case IPPROTO_IGMP:
handle_igmp();
return TransportProtocol::IGMP;
/* if we have an extension headers, dont leave from loop,
* keep find a protocol type
*/
case IPPROTO_HOPOPTS:
case IPPROTO_ROUTING:
case IPPROTO_DSTOPTS: {
const auto* ext = reinterpret_cast<const ip6_ext*>(ptr);
hdr = ext->ip6e_nxt;
ptr += (ext->ip6e_len + 1) * 8;
break;
}
case IPPROTO_FRAGMENT: {
const auto* frag = reinterpret_cast<const ip6_frag*>(ptr);
hdr = frag->ip6f_nxt;
ptr += sizeof(ip6_frag);
break;
}
default:
return TransportProtocol::UNKNOWN;
} }
} }
ptr = nullptr; ptr = nullptr;
} }
void IPv6::handle_tcp() { void IPv6::handle_tcp() {
const auto tcp = reinterpret_cast<const tcphdr*>(ptr); const auto tcp = reinterpret_cast<const tcphdr *>(ptr);
dest_port = ntohs(tcp->dest); dest_port = ntohs(tcp->dest);
src_port = ntohs(tcp->source); src_port = ntohs(tcp->source);
payload_ptr = (const uint8_t*)tcp + tcp->doff * 4; payload_ptr = reinterpret_cast<const uint8_t *>(tcp) + tcp->doff * 4;
payload_len = ntohs(ip_hdr->ip6_plen) - tcp->doff * 4; payload_len = ntohs(ip_hdr->ip6_plen) - tcp->doff * 4;
protocol = TransportProtocol::TCP;
ptr = nullptr; ptr = nullptr;
} }
void IPv6::handle_udp() { void IPv6::handle_udp() {
const auto udp = reinterpret_cast<const udphdr*>(ptr); const auto udp = reinterpret_cast<const udphdr *>(ptr);
dest_port = ntohs(udp->dest); dest_port = ntohs(udp->dest);
src_port = ntohs(udp->source); src_port = ntohs(udp->source);
payload_ptr = (const uint8_t*)udp + sizeof(udphdr); payload_ptr = reinterpret_cast<const uint8_t *>(udp) + sizeof(udphdr);
payload_len = ntohs(udp->len) - sizeof(udphdr); payload_len = ntohs(udp->len) - sizeof(udphdr);
protocol = TransportProtocol::UDP;
ptr = nullptr; ptr = nullptr;
} }
void IPv6::handle_icmp() { void IPv6::handle_icmp() {
protocol = TransportProtocol::ICMP;
ptr = nullptr; ptr = nullptr;
} }
void IPv6::handle_icmpv6() { void IPv6::handle_icmpv6() {
protocol = TransportProtocol::ICMP6;
payload_len = ntohs(ip_hdr->ip6_plen) - sizeof(icmp6_hdr); payload_len = ntohs(ip_hdr->ip6_plen) - sizeof(icmp6_hdr);
ptr = nullptr; ptr = nullptr;
} }
void IPv6::handle_igmp() { void IPv6::handle_igmp() {
protocol = TransportProtocol::IGMP;
ptr = nullptr; ptr = nullptr;
} }
std::string IPv6::get_source() { uint16_t IPv6::get_src_port() { return src_port; }
char src[INET6_ADDRSTRLEN];
inet_ntop(AF_INET6, &ip_hdr->ip6_src, src, sizeof(src));
return std::string(src);
}
std::string IPv6::get_dest() {
char dst[INET6_ADDRSTRLEN];
inet_ntop(AF_INET6, &ip_hdr->ip6_dst, dst, sizeof(dst));
return std::string(dst);
}
uint16_t IPv6::get_src_port() {
return src_port;
}
uint16_t IPv6::get_dest_port() {
return dest_port;
}
uint16_t IPv6::get_dest_port() { return dest_port; }

View File

@ -2,14 +2,12 @@
#include <cstring> #include <cstring>
ApplicationProtocol Packet::get_application_protocol() { ApplicationProtocol Packet::get_application_protocol() {
if (!payload_ptr || payload_len < 4) goto check_port; if (!payload_ptr || payload_len < 4)
goto check_port;
if (transport_protocol == TransportProtocol::TCP) { if (transport_protocol == TransportProtocol::TCP) {
if (!memcmp(payload_ptr, "GET ", 4) || if (!memcmp(payload_ptr, "GET ", 4) || !memcmp(payload_ptr, "POST", 4) || !memcmp(payload_ptr, "HEAD", 4) ||
!memcmp(payload_ptr, "POST", 4) || !memcmp(payload_ptr, "PUT ", 4) || !memcmp(payload_ptr, "HTTP", 4))
!memcmp(payload_ptr, "HEAD", 4) ||
!memcmp(payload_ptr, "PUT ", 4) ||
!memcmp(payload_ptr, "HTTP", 4))
return ApplicationProtocol::HTTP; return ApplicationProtocol::HTTP;
} }
@ -25,25 +23,35 @@ check_port:
uint16_t port = (src_port < dst_port) ? src_port : dst_port; uint16_t port = (src_port < dst_port) ? src_port : dst_port;
if (transport_protocol == TransportProtocol::TCP) { if (transport_protocol == TransportProtocol::TCP) {
switch (port) { switch (port) {
case 21: return ApplicationProtocol::FTP; case 21:
case 22: return ApplicationProtocol::SSH; return ApplicationProtocol::FTP;
case 25: return ApplicationProtocol::SMTP; case 22:
case 53: return ApplicationProtocol::DNS; return ApplicationProtocol::SSH;
case 80: return ApplicationProtocol::HTTP; case 25:
case 443: return ApplicationProtocol::HTTPS; return ApplicationProtocol::SMTP;
default: return ApplicationProtocol::UNKNOWN; case 53:
return ApplicationProtocol::DNS;
case 80:
return ApplicationProtocol::HTTP;
case 443:
return ApplicationProtocol::HTTPS;
default:
return ApplicationProtocol::UNKNOWN;
} }
} }
if (transport_protocol == TransportProtocol::UDP) { if (transport_protocol == TransportProtocol::UDP) {
switch (port) { switch (port) {
case 53: return ApplicationProtocol::DNS; case 53:
case 443: return ApplicationProtocol::QUIC; return ApplicationProtocol::DNS;
case 123: return ApplicationProtocol::NTP; case 443:
default: return ApplicationProtocol::UNKNOWN; return ApplicationProtocol::QUIC;
case 123:
return ApplicationProtocol::NTP;
default:
return ApplicationProtocol::UNKNOWN;
} }
} }
return ApplicationProtocol::UNKNOWN; return ApplicationProtocol::UNKNOWN;
} }

View File

@ -1,11 +1,8 @@
#include "../../include/stats/protocolStats.hpp" #include "../../include/stats/protocolStats.hpp"
#include "ftxui/dom/table.hpp" #include "ftxui/dom/table.hpp"
#include <fstream>
Stats::Stats() { last_tick = std::chrono::steady_clock::now(); }
Stats::Stats() {
last_tick = std::chrono::steady_clock::now();
}
/** /**
* @brief Aggregates a newly captured packet. * @brief Aggregates a newly captured packet.
* *
@ -19,17 +16,17 @@ Stats::Stats() {
* Must be called only from capture thread. * Must be called only from capture thread.
* Protected by mutex. * Protected by mutex.
*/ */
void Stats::add_packet(Packet &packet) { void Stats::add_packet(const Packet &packet) {
std::lock_guard<std::mutex> lock(mtx); std::lock_guard<std::mutex> lock(mtx);
++snapshot.total_p; ++snapshot.total_p;
snapshot.total_b += packet.total_len; snapshot.total_b += packet.total_len;
auto& t = transport_map[packet.transport_protocol]; auto &t = transport_map[packet.transport_protocol];
t.packets++; t.packets++;
t.bytes += packet.total_len; t.bytes += packet.total_len;
auto& a = application_map[packet.application_protocol]; auto &a = application_map[packet.application_protocol];
a.packets++; a.packets++;
a.bytes += packet.payload_len; a.bytes += packet.payload_len;
@ -42,32 +39,45 @@ void Stats::add_packet(Packet &packet) {
auto key = std::make_pair(packet.src, packet.dst); auto key = std::make_pair(packet.src, packet.dst);
pairs[key].packets++; pairs[key].packets++;
pairs[key].bytes += packet.total_len; pairs[key].bytes += packet.total_len;
} }
const char *transport_to_str(TransportProtocol p) {
const char* transport_to_str(TransportProtocol p) {
switch (p) { switch (p) {
case TransportProtocol::TCP: return "TCP"; case TransportProtocol::TCP:
case TransportProtocol::UDP: return "UDP"; return "TCP";
case TransportProtocol::ICMP: return "ICMP"; case TransportProtocol::UDP:
case TransportProtocol::ICMP6: return "ICMP6"; return "UDP";
case TransportProtocol::IGMP: return "IGMP"; case TransportProtocol::ICMP:
default: return "UNKNOWN"; return "ICMP";
case TransportProtocol::ICMP6:
return "ICMP6";
case TransportProtocol::IGMP:
return "IGMP";
default:
return "UNKNOWN";
} }
} }
const char* app_to_str(ApplicationProtocol p) { const char *app_to_str(ApplicationProtocol p) {
switch (p) { switch (p) {
case ApplicationProtocol::HTTP: return "HTTP"; case ApplicationProtocol::HTTP:
case ApplicationProtocol::HTTPS: return "HTTPS"; return "HTTP";
case ApplicationProtocol::DNS: return "DNS"; case ApplicationProtocol::HTTPS:
case ApplicationProtocol::FTP: return "FTP"; return "HTTPS";
case ApplicationProtocol::SSH: return "SSH"; case ApplicationProtocol::DNS:
case ApplicationProtocol::SMTP: return "SMTP"; return "DNS";
case ApplicationProtocol::QUIC: return "QUIC"; case ApplicationProtocol::FTP:
case ApplicationProtocol::NTP: return "NTP"; return "FTP";
default: return "UNKNOWN"; case ApplicationProtocol::SSH:
return "SSH";
case ApplicationProtocol::SMTP:
return "SMTP";
case ApplicationProtocol::QUIC:
return "QUIC";
case ApplicationProtocol::NTP:
return "NTP";
default:
return "UNKNOWN";
} }
} }
@ -83,29 +93,19 @@ const char* app_to_str(ApplicationProtocol p) {
void Stats::update_transport_stats() { void Stats::update_transport_stats() {
std::lock_guard<std::mutex> lock(mtx); std::lock_guard<std::mutex> lock(mtx);
snapshot.transport_rows.clear(); snapshot.transport_rows.clear();
snapshot.transport_rows.push_back({ "Proto", "Packets", "Bytes", "%" }); snapshot.transport_rows.push_back({"Proto", "Packets", "Bytes", "%"});
std::vector<std::pair<TransportProtocol, protocolStats>> tps( std::vector<std::pair<TransportProtocol, protocolStats>> tps(transport_map.begin(), transport_map.end());
transport_map.begin(), transport_map.end() std::sort(tps.begin(), tps.end(), [](auto &a, auto &b) { return a.second.packets > b.second.packets; });
);
std::sort(tps.begin(), tps.end(),
[](auto& a, auto& b) {
return a.second.packets > b.second.packets;
});
for (const auto& [proto, stats] : tps) { for (const auto &[proto, stats] : tps) {
double percent = snapshot.total_b ? stats.bytes * 100.0 / snapshot.total_b : 0.0; double percent = snapshot.total_b ? stats.bytes * 100.0 / snapshot.total_b : 0.0;
snapshot.transport_rows.push_back({ snapshot.transport_rows.push_back({transport_to_str(proto), std::to_string(stats.packets),
transport_to_str(proto), std::format("{:.2f}", stats.bytes / (1024.0 * 1024.0)),
std::to_string(stats.packets), std::format("{:.2f}", percent)});
std::format("{:.2f}", stats.bytes / (1024.0 * 1024.0)),
std::format("{:.2f}", percent)
});
} }
} }
/** /**
* @brief Rebuilds application protocol snapshot. * @brief Rebuilds application protocol snapshot.
* *
@ -114,29 +114,19 @@ void Stats::update_transport_stats() {
*/ */
void Stats::update_application_stats() { void Stats::update_application_stats() {
std::lock_guard<std::mutex> lock(mtx); std::lock_guard<std::mutex> lock(mtx);
std::vector<std::pair<ApplicationProtocol, protocolStats>> apps( std::vector<std::pair<ApplicationProtocol, protocolStats>> apps(application_map.begin(), application_map.end());
application_map.begin(), application_map.end()
);
std::sort(apps.begin(), apps.end(), std::sort(apps.begin(), apps.end(), [](auto &a, auto &b) { return a.second.packets > b.second.packets; });
[](auto& a, auto& b) {
return a.second.packets > b.second.packets;
});
snapshot.app_rows.clear(); snapshot.app_rows.clear();
snapshot.app_rows.push_back({ "Proto", "Packets", "Bytes (MB)", "%" }); snapshot.app_rows.push_back({"Proto", "Packets", "Bytes (MB)", "%"});
for (const auto& [proto, s] : apps) { for (const auto &[proto, s] : apps) {
double percent = snapshot.total_b double percent = snapshot.total_b ? s.bytes * 100.0 / snapshot.total_b : 0.0;
? s.bytes * 100.0 / snapshot.total_b
: 0.0;
snapshot.app_rows.push_back({ snapshot.app_rows.push_back({app_to_str(proto), std::to_string(s.packets),
app_to_str(proto), std::format("{:.2f}", s.bytes / (1024.0 * 1024.0)),
std::to_string(s.packets), std::format("{:.2f}", percent)});
std::format("{:.2f}", s.bytes / (1024.0 * 1024.0)),
std::format("{:.2f}", percent)
});
} }
} }
@ -152,23 +142,16 @@ void Stats::update_ip_stats(size_t limit) {
snapshot.rows.clear(); snapshot.rows.clear();
snapshot.rows.push_back({"IP Address", "Packets TX", "Packets RX"}); snapshot.rows.push_back({"IP Address", "Packets TX", "Packets RX"});
std::vector<std::pair<std::string, IPStats>> ips( std::vector<std::pair<std::string, IPStats>> ips(ip_map.begin(), ip_map.end());
ip_map.begin(), ip_map.end()
);
std::sort(ips.begin(), ips.end(), std::sort(ips.begin(), ips.end(), [](auto &a, auto &b) { return a.second.packets_sent > b.second.packets_sent; });
[](auto& a, auto& b) {
return a.second.packets_sent > b.second.packets_sent;
});
size_t count = 0; size_t count = 0;
for (const auto& [ip, s] : ips) { for (const auto &[ip, s] : ips) {
if (count++ >= limit) break; if (count++ >= limit)
snapshot.rows.push_back({ip, break;
"TX: " + std::to_string(s.packets_sent), snapshot.rows.push_back(
"RX: " + std::to_string(s.packets_received)} {ip, "TX: " + std::to_string(s.packets_sent), "RX: " + std::to_string(s.packets_received)});
);
} }
} }
@ -183,17 +166,15 @@ void Stats::update_ip_stats(size_t limit) {
void Stats::update_pairs(size_t limit) { void Stats::update_pairs(size_t limit) {
std::lock_guard<std::mutex> lock(mtx); std::lock_guard<std::mutex> lock(mtx);
std::vector<std::pair<std::pair<std::string, std::string>, protocolStats>> vec(pairs.begin(), pairs.end()); std::vector<std::pair<std::pair<std::string, std::string>, protocolStats>> vec(pairs.begin(), pairs.end());
std::sort(vec.begin(), vec.end(), std::sort(vec.begin(), vec.end(), [](auto &a, auto &b) { return a.second.bytes > b.second.bytes; });
[](auto& a, auto& b) {
return a.second.bytes > b.second.bytes;
});
snapshot.pairs_rows.clear(); snapshot.pairs_rows.clear();
snapshot.pairs_rows.push_back({ "Source", "Destination", "bytes received", "%" }); snapshot.pairs_rows.push_back({"Source", "Destination", "bytes received", "%"});
size_t count = 0; size_t count = 0;
for (const auto& [pair, s] : vec) { for (const auto &[pair, s] : vec) {
if (count++ >= limit) break; if (count++ >= limit)
break;
double percent = snapshot.total_b ? (s.bytes * 100.0 / snapshot.total_b) : 0.0; double percent = snapshot.total_b ? (s.bytes * 100.0 / snapshot.total_b) : 0.0;
snapshot.pairs_rows.push_back({ snapshot.pairs_rows.push_back({
pair.first, pair.first,
@ -202,16 +183,15 @@ void Stats::update_pairs(size_t limit) {
std::format("{:.2f}", percent), std::format("{:.2f}", percent),
}); });
} }
} }
void Stats::update_packets(){ void Stats::update_packets() {
std::lock_guard lock(mtx); std::lock_guard lock(mtx);
snapshot.packets_rows.clear(); snapshot.packets_rows.clear();
snapshot.packets_rows.push_back({ "IPVersion", "Transport protocol", "Source", "Destination", "App protocol"}); snapshot.packets_rows.push_back({"IPVersion", "Transport protocol", "Source", "Destination", "App protocol"});
for (auto& packet : packets) { for (auto &packet : packets) {
snapshot.packets_rows.push_back({ snapshot.packets_rows.push_back({
packet.ip_version == IPVersion::v4 ? "IPv4" : "IPv6", packet.ip_version == IPVersion::v4 ? "IPv4" : "IPv6",
transport_to_str(packet.transport_protocol), transport_to_str(packet.transport_protocol),
@ -264,13 +244,11 @@ void Stats::update_bandwidth() {
last_b = snapshot.total_b; last_b = snapshot.total_b;
last_tick = now; last_tick = now;
const double alpha = 0.2; const double alpha = 0.2;
smooth_bandwidth = smooth_bandwidth = alpha * snapshot.bandwidth + (1.0 - alpha) * smooth_bandwidth;
alpha * snapshot.bandwidth + (1.0 - alpha) * smooth_bandwidth;
snapshot.bandwidth_history.push_back({ ts, smooth_bandwidth }); snapshot.bandwidth_history.push_back({ts, smooth_bandwidth});
} }
snapshot.max_bandwidth = std::max(snapshot.max_bandwidth, snapshot.bandwidth); snapshot.max_bandwidth = std::max(snapshot.max_bandwidth, snapshot.bandwidth);
} }
/** /**
@ -284,27 +262,23 @@ void Stats::update_bandwidth() {
* - Bandwidth history * - Bandwidth history
*/ */
void Stats::export_csv(const std::string& filename) { void Stats::export_csv(const std::string &filename) {
std::lock_guard<std::mutex> lock(mtx); std::lock_guard<std::mutex> lock(mtx);
std::ofstream file(filename); std::ofstream file(filename);
if (!file.is_open()) return; if (!file.is_open())
return;
file << "summary\n"; file << "summary\n";
file << "total_packets,total_bytes,bandwidth\n"; file << "total_packets,total_bytes,bandwidth\n";
file << snapshot.total_p << "," file << snapshot.total_p << "," << snapshot.total_b << "," << snapshot.bandwidth << "\n\n";
<< snapshot.total_b << ","
<< snapshot.bandwidth << "\n\n";
// ===== Transport protocols ===== // ===== Transport protocols =====
file << "transport_protocols\n"; file << "transport_protocols\n";
file << "protocol,packets,bytes,percent\n"; file << "protocol,packets,bytes,percent\n";
for (const auto& [proto, s] : transport_map) { for (const auto &[proto, s] : transport_map) {
double percent = snapshot.total_b ? (s.bytes * 100.0 / snapshot.total_b) : 0.0; double percent = snapshot.total_b ? (s.bytes * 100.0 / snapshot.total_b) : 0.0;
file << transport_to_str(proto) << "," file << transport_to_str(proto) << "," << s.packets << "," << s.bytes << "," << percent << "\n";
<< s.packets << ","
<< s.bytes << ","
<< percent << "\n";
} }
file << "\n"; file << "\n";
@ -312,10 +286,8 @@ void Stats::export_csv(const std::string& filename) {
file << "application_protocols\n"; file << "application_protocols\n";
file << "protocol,packets,payload_bytes\n"; file << "protocol,packets,payload_bytes\n";
for (const auto& [proto, s] : application_map) { for (const auto &[proto, s] : application_map) {
file << static_cast<int>(proto) << "," file << static_cast<int>(proto) << "," << s.packets << "," << s.bytes << "\n";
<< s.packets << ","
<< s.bytes << "\n";
} }
file << "\n"; file << "\n";
@ -323,24 +295,19 @@ void Stats::export_csv(const std::string& filename) {
file << "ip_stats\n"; file << "ip_stats\n";
file << "ip,packets_sent,packets_received,bytes_sent,bytes_received\n"; file << "ip,packets_sent,packets_received,bytes_sent,bytes_received\n";
for (const auto& [ip, s] : ip_map) { for (const auto &[ip, s] : ip_map) {
file << ip << "," file << ip << "," << s.packets_sent << "," << s.packets_received << "," << s.bytes_sent << ","
<< s.packets_sent << ","
<< s.packets_received << ","
<< s.bytes_sent << ","
<< s.bytes_received << "\n"; << s.bytes_received << "\n";
} }
// bandwidth
//bandwidth
file << "time,bandwidth\n"; file << "time,bandwidth\n";
for (const auto& p : snapshot.bandwidth_history) { for (const auto &p : snapshot.bandwidth_history) {
file << p.timestamp << "," << p.bytes_per_sec << "\n"; file << p.timestamp << "," << p.bytes_per_sec << "\n";
} }
file.close(); file.close();
} }
/** /**
* @brief Exports statistics to JSON format. * @brief Exports statistics to JSON format.
@ -350,10 +317,11 @@ void Stats::export_csv(const std::string& filename) {
* - Visualization tools * - Visualization tools
* - Data pipelines * - Data pipelines
*/ */
void Stats::export_json(const std::string& filename) { void Stats::export_json(const std::string &filename) {
std::lock_guard<std::mutex> lock(mtx); std::lock_guard<std::mutex> lock(mtx);
std::ofstream file(filename); std::ofstream file(filename);
if (!file.is_open()) return; if (!file.is_open())
return;
file << "{\n"; file << "{\n";
@ -367,8 +335,9 @@ void Stats::export_json(const std::string& filename) {
// ===== Transport ===== // ===== Transport =====
file << " \"transport\": [\n"; file << " \"transport\": [\n";
bool first = true; bool first = true;
for (const auto& [proto, s] : transport_map) { for (const auto &[proto, s] : transport_map) {
if (!first) file << ",\n"; if (!first)
file << ",\n";
first = false; first = false;
double percent = snapshot.total_b ? (s.bytes * 100.0 / snapshot.total_b) : 0.0; double percent = snapshot.total_b ? (s.bytes * 100.0 / snapshot.total_b) : 0.0;
@ -385,8 +354,9 @@ void Stats::export_json(const std::string& filename) {
// ===== IP stats ===== // ===== IP stats =====
file << " \"top_ips\": [\n"; file << " \"top_ips\": [\n";
first = true; first = true;
for (const auto& [ip, s] : ip_map) { for (const auto &[ip, s] : ip_map) {
if (!first) file << ",\n"; if (!first)
file << ",\n";
first = false; first = false;
file << " {\n"; file << " {\n";
@ -402,8 +372,9 @@ void Stats::export_json(const std::string& filename) {
file << ",\n \"communication_pairs\": [\n"; file << ",\n \"communication_pairs\": [\n";
first = true; first = true;
for (const auto& [pair, s] : pairs) { for (const auto &[pair, s] : pairs) {
if (!first) file << ",\n"; if (!first)
file << ",\n";
first = false; first = false;
file << " {\n"; file << " {\n";