add: cpp version, moved code to separate directories

This commit is contained in:
deniskhud 2026-02-22 13:38:02 +01:00
parent 1cd847ba8b
commit d67499febf
44 changed files with 2002 additions and 0 deletions

View File

@ -0,0 +1,48 @@
cmake_minimum_required(VERSION 3.31)
project(network-traffic-analyzer)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
link_libraries(pcap)
find_package(Boost REQUIRED COMPONENTS program_options)
add_executable(network-traffic-analyzer main.cpp
"include/capture/pcapCapture.hpp"
src/capture/pcapCapture.cpp
"include/cli/argsParse.hpp"
include/packet/packet.hpp
include/packet/IP.hpp
src/packet/IP.cpp
include/stats/protocolStats.hpp
src/stats/protocolStats.cpp
src/packet/packet.cpp
src/cli/argsParse.cpp
include/cli/filter.hpp
src/cli/filter.cpp
include/TUI/view.hpp
src/TUI/view.cpp
)
include(FetchContent)
set(FETCHCONTENT_UPDATES_DISCONNECTED ON)
set(FETCHCONTENT_QUIET OFF)
FetchContent_Declare(ftxui
GIT_REPOSITORY https://github.com/arthursonzogni/ftxui.git
GIT_TAG v5.0.0
GIT_PROGRESS TRUE
GIT_SHALLOW TRUE
EXCLUDE_FROM_ALL
)
FetchContent_MakeAvailable(ftxui)
target_link_libraries(network-traffic-analyzer
Boost::program_options
ftxui::screen
ftxui::dom
ftxui::component
)

View File

@ -0,0 +1,87 @@
```ruby
▄▄▄▄▄▄ ▄▄▄▄ ▄▄▄ ▄▄▄▄▄ ▄▄▄▄▄ ▄▄ ▄▄▄▄ ▄▄▄ ▄▄ ▄▄ ▄▄▄ ▄▄ ▄▄ ▄▄ ▄▄▄▄▄ ▄▄▄▄▄ ▄▄▄▄
██ ██▄█▄ ██▀██ ██▄▄ ██▄▄ ██ ██▀▀▀ ██▀██ ███▄██ ██▀██ ██ ▀███▀ ▄█▀ ██▄▄ ██▄█▄
██ ██ ██ ██▀██ ██ ██ ██ ▀████ ██▀██ ██ ▀██ ██▀██ ██▄▄▄ █ ▄██▄▄ ██▄▄▄ ██ ██
```
>A high-performance CLI network analyzer built with libpcap for raw packet capture and FTXUI for a fully interactive terminal UI.
The application captures packets directly from a network interface, parses protocol headers manually, aggregates statistics in real time
---
![Preview](example.png)
> [!IMPORTANT]
> Packet capture requires elevated privileges.
Run with:
```bash
sudo ./network-traffic-analyzer
```
Or grant capabilities:
```bash
sudo setcap cap_net_raw,cap_net_admin=eip ./network-traffic-analyzer
```
---
# Features
1) ## Live Packet Capture
- Capture traffic from a selected network interface
- Support for BPF filters (e.g. tcp, port 80, udp)
- Real-time processing using libpcap
2) ## Real-Time Statistics Engine
- Total packets & traffic volume
- Transport protocol distribution (TCP / UDP / ICMP)
- Application-level classification (port-based)
- Top IP addresses
- Top source > destination pairs
3) ## Flexible Capture Modes
- Live capture from selected network interface (-i, --interface)
- Offline analysis from .pcap file (-r, --offline)
- Packet count limit (-c)
- Time limit for capture (-t)
- Interface discovery (--interfaces)
> [!TIP]
> For the complete list of CLI options, use:
> `--help`
# Technologies
- C++20+
- Boost::program_options
- libpcap
- FTXUI
- CMake
# Build
```
mkdir build && cd build
cmake ..
make
```
# Usage Example
### Live capture on eth0
```
sudo ./network-traffic-analyzer -i eth0
```
### Capture 100 packets
```
sudo ./network-traffic-analyzer -i wlan0 -c 100
```
### Analyze offline pcap file
```
sudo ./network-traffic-analyzer --offline traffic.pcap
```
### Export results (json / csv)
```
sudo ./network-traffic-analyzer --json result.json --csv result.csv
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 KiB

View File

@ -0,0 +1,40 @@
#ifndef VIEW_HPP
#define VIEW_HPP
#include "../stats/protocolStats.hpp"
#include <ftxui/dom/elements.hpp>
class View {
public:
ftxui::Element render(
const StatsSnapshot& data,
const std::string& interface,
const std::string& filter,
bool capture_finished,
std::chrono::seconds timer
);
private:
ftxui::Element render_header(
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_application(const StatsSnapshot& data);
ftxui::Element render_ip(const StatsSnapshot& data);
ftxui::Element render_pairs(const StatsSnapshot& data);
ftxui::Element render_bandwidth(const StatsSnapshot& data);
ftxui::Element render_packets(const StatsSnapshot& data);
ftxui::Element render_footer(
bool capture_finished,
std::chrono::seconds timer
);
};
#endif //VIEW_HPP

View File

@ -0,0 +1,107 @@
#ifndef PCAPCAPTURE_HPP
#define PCAPCAPTURE_HPP
#include <memory>
#include <queue>
#include <pcap/pcap.h>
#include <thread>
#include <deque>
//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/tcp.h>
#include <netinet/igmp.h>
#define SNAP_LEN 1518
}
#include "../packet/IP.hpp"
#include "../../include/stats/protocolStats.hpp"
#include "../packet/packet.hpp"
/**
* Packet capture engine based on libpcap
*
* Supports:
* - Live capture from network interface
* - Offline capture from .pcap file
* - BPF filtering
* - Separate capture thread (for live mode)
*
* Workflow:
* initialize() -> load interfaces
* set_capabilities()-> configure capture parameters
* start() -> start live capture (threaded)
* start_offline() -> process file synchronously
* stop() -> stop capture and cleanup
*/
class PcapCapture {
private:
/* libpcap error buffer */
char errbuf[PCAP_ERRBUF_SIZE];
/* filter expression (compiled before capture) */
std::string filter_exp = "";
/* compiled filter program (expression) */
struct bpf_program fp = {};
/* Active pcap handle */
pcap_t *handle = nullptr;
/* Network mask and IP */
bpf_u_int32 mask = 0;
bpf_u_int32 net = 0;
/* Number of packets to capture */
int num_packets = 0;
/* Linked list of available interfaces */
pcap_if_t *interfaces = nullptr;
/* Selected network interface */
std::string interface;
/**
* Static wrapper required by C-style libpcap callback.
*
* Since libpcap expects a C function pointer,
* we use a static function and forward the call
* to the class instance.
*/
static void callback(u_char *args, const struct pcap_pkthdr *header, const u_char *packet);
// packet processing logic
void got_packet(const struct pcap_pkthdr *header, const u_char *packet);
/* Separate thread used for live capture */
std::thread thread;
std::atomic<bool> running{false};
public:
void print_interfaces();
bool isRunning() {
return running;
}
void setRunning(bool running) {
this->running = running;
}
/* 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 initialize();
void start();
void start_offline(std::string fpath);
void stop();
};
#endif //PCAPCAPTURE_HPP

View File

@ -0,0 +1,14 @@
#ifndef ARGSPARSE_HPP
#define ARGSPARSE_HPP
#include <boost/program_options.hpp>
namespace po = boost::program_options;
struct argsParser {
po::options_description desc;
po::variables_map vm;
void print_help() const;
argsParser(int argc, char** argv);
};
#endif //ARGSPARSE_HPP

View File

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

View File

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

View File

@ -0,0 +1,59 @@
#ifndef PACKET_HPP
#define PACKET_HPP
#include <cstdint>
#include <utility>
#include <string>
enum IPVersion {
v4, v6,
};
enum class TransportProtocol {
TCP = 1,
UDP = 2,
ICMP = 3,
ICMP6 = 4,
IGMP = 5,
UNKNOWN = -1,
};
enum class ApplicationProtocol {
HTTP,
HTTPS,
DNS,
FTP,
SSH,
SMTP,
QUIC,
NTP,
UNKNOWN,
};
struct Packet {
IPVersion ip_version;
TransportProtocol transport_protocol;
ApplicationProtocol application_protocol;
//src address
std::string src;
//dest address
std::string dst;
uint16_t src_port;
uint16_t dst_port;
uint32_t total_len;
uint16_t payload_len;
const uint8_t* payload_ptr;
Packet(IPVersion version, TransportProtocol protocol, std::string src, std::string dst,
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),
src(std::move(src)), dst(std::move(dst)), src_port(src_port), dst_port(dst_port), total_len(total_len), payload_len(payload),
payload_ptr(payload_ptr)
{
this->application_protocol = get_application_protocol();
}
Packet() { }
private:
ApplicationProtocol get_application_protocol();
};
#endif //PACKET_HPP

View File

@ -0,0 +1,120 @@
#ifndef PROTOCOLSTATS_HPP
#define PROTOCOLSTATS_HPP
#include <chrono>
#include <cstdint>
#include "../packet/packet.hpp"
#include <unordered_map>
#include <filesystem>
#include <fstream>
#include <map>
#include <queue>
#include "ftxui/dom/elements.hpp"
using namespace ftxui;
struct protocolStats {
uint32_t packets = 0;
uint32_t bytes = 0;
};
struct trafficStats {
uint32_t total_packets = 0;
uint32_t total_bytes = 0;
};
struct IPStats {
uint32_t bytes_sent = 0;
uint32_t bytes_received = 0;
uint32_t packets_sent = 0;
uint32_t packets_received = 0;
};
struct BandwidthPoint {
double timestamp;
double bytes_per_sec;
};
struct StatsSnapshot {
std::vector<std::vector<std::string>> transport_rows;
std::vector<std::vector<std::string>> app_rows;
std::vector<std::vector<std::string>> rows;
std::vector<std::vector<std::string>> pairs_rows;
std::vector<std::vector<std::string>> packets_rows;
uint32_t total_p = 0, total_b = 0;
//bandwidth
std::vector<BandwidthPoint> bandwidth_history;
double bandwidth = 0;
mutable double max_bandwidth = 0;
};
/**
* @brief Thread-safe statistics engine.
*
* Responsible for:
* - Aggregating packet statistics
* - Maintaining protocol distributions
* - Tracking IP traffic
* - Calculating bandwidth
* - Providing snapshot for UI rendering
*
* All write operations are protected by mutex.
*/
class Stats {
private:
std::mutex mtx;
uint32_t last_b = 0;
std::chrono::steady_clock::time_point last_tick;
std::unordered_map<TransportProtocol, protocolStats> transport_map;
std::unordered_map<ApplicationProtocol, protocolStats> application_map;
std::unordered_map<std::string, IPStats> ip_map;
std::map<std::pair<std::string, std::string>, protocolStats> pairs;
std::deque<Packet> packets;
int limit_packets = 10;
public:
void push(const Packet& p) {
std::lock_guard<std::mutex> lock(mtx);
if (packets.size() > static_cast<long unsigned int>(limit_packets)) {
packets.pop_front();
}
packets.push_back(p);
}
StatsSnapshot snapshot;
void update_bandwidth();
double smooth_value(size_t i, size_t start);
double smooth_bandwidth = 0.0;
void set_packets_limit(int limit) {
limit_packets = limit;
}
void add_packet(Packet &packet);
void update_transport_stats();
void update_application_stats();
void update_ip_stats(size_t limit);
void update_pairs(size_t limit = 10);
void update_packets();
void export_csv(const std::string& filename);
void export_json(const std::string& filename);
Stats();
};
#endif //PROTOCOLSTATS_HPP

View File

@ -0,0 +1,153 @@
#include "include/capture/pcapCapture.hpp"
#include <iostream>
#include <pcap/pcap.h>
#include <boost/program_options.hpp>
#include "include/cli/filter.hpp"
#include <ftxui/component/screen_interactive.hpp>
#include <ftxui/component/component.hpp>
#include <ftxui/component/component_options.hpp>
#include "include/cli/argsParse.hpp"
#include "include/TUI/view.hpp"
#define SNAP_LEN 1518
int main(int argc, char **argv)
{
/* initialize capture */
PcapCapture capture;
capture.initialize();
/* initialize stats */
Stats stats;
/* initializing the command line parser */
argsParser parser(argc, argv);
/* check helper flags */
if (parser.vm.contains("help")) {
parser.print_help();
return 0;
}
if (parser.vm.contains("interfaces")) {
capture.print_interfaces();
return 0;
}
/* take the arguments of the flags into variables */
std::string interface = parser.vm["interface"].as<std::string>();
int count = parser.vm["count"].as<int>();
int limit = parser.vm["limit"].as<int>();
int time = parser.vm["time"].as<int>();
std::string filterString = "";
/* get a filter, use vector for multiple */
std::vector<filter> filters;
if (parser.vm.contains("filter")) {
auto& f = parser.vm["filter"].as<std::vector<std::string>>();
for (auto& x : f) {
filters.push_back(parse(x));
filterString += x + " ";
}
}
/* converting the filter to a pcap readable string */
std::string expression = get_bpf_filter(filters);
bool isOffline = parser.vm.contains("offline");
/* set the flags to capture engine */
capture.set_capabilities(interface, count, expression, limit, &stats);
std::atomic<bool> capture_finished = false;
/* if we capture packets offline, we read the file in full, then print the result */
if (isOffline) {
capture.start_offline(parser.vm["offline"].as<std::string>());
/* full recalculation of statistics after file processing */
stats.update_packets();
stats.update_application_stats();
stats.update_transport_stats();
stats.update_ip_stats(10);
stats.update_pairs();
stats.update_bandwidth();
}
/* otherwise start live capture */
else {
capture.start();
}
/* UI */
using namespace ftxui;
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::seconds timer{0};
auto renderer = Renderer([&] {
auto snapshot = stats.snapshot;
return view.render(snapshot,
interface,
filterString,
capture_finished,
timer);
});
auto component = CatchEvent(renderer, [&](Event e) {
if (e == Event::Character('q') || e == Event::Escape) {
capture.stop();
screen.Exit();
}
return true;
});
std::thread updater;
if (!isOffline) {
updater = std::thread([&] {
while (capture.isRunning()) {
stats.update_packets();
stats.update_application_stats();
stats.update_transport_stats();
stats.update_ip_stats(10);
stats.update_pairs();
stats.update_bandwidth();
screen.PostEvent(Event::Custom);
std::this_thread::sleep_for(std::chrono::milliseconds(300));
auto current_time = std::chrono::steady_clock::now();
timer = std::chrono::duration_cast<std::chrono::seconds>(
current_time - begin);
if (timer >= std::chrono::seconds(time))
break;
}
capture_finished = true;
screen.PostEvent(Event::Custom);
});
}
screen.Loop(component);
if (updater.joinable())
updater.join();
/* check export flags */
if (parser.vm.contains("csv")) {
stats.export_csv(parser.vm["csv"].as<std::string>());
}
if (parser.vm.contains("json")) {
stats.export_json(parser.vm["json"].as<std::string>());
}
return 0;
}

View File

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

View File

@ -0,0 +1,206 @@
#include "../../include/capture/pcapCapture.hpp"
#include "../../include/stats/protocolStats.hpp"
/* get a list of all available network interfaces */
void PcapCapture::initialize() {
/* find all devs available in network, save them to pcap_if_t struct (interfaces) */
if (pcap_findalldevs(&interfaces, errbuf) == -1) {
fprintf(stderr, "Error: pcap_findalldevs has been failed - %s\n", errbuf);
}
}
/**
* Start live packet capture.
*
* Steps:
* 1. Resolve network mask
* 2. Open device in promiscuous mode
* 3. Compile and apply BPF filter (if provided)
* 4. Start pcap_loop in a separate thread
*/
void PcapCapture::start() {
// getting the netmask of the interface
if (pcap_lookupnet(interface.c_str(), &net, &mask, errbuf) == -1) {
fprintf(stderr, "Couldn't get netmask for device %s: %s\n",
interface.c_str(), errbuf);
net = 0;
mask = 0;
}
/* open capture device */
handle = pcap_open_live(interface.c_str(), SNAP_LEN, 1, 1000, errbuf);
if (handle == nullptr) {
fprintf(stderr, "Couldn't open device %s: %s\n", interface.c_str(), errbuf);
exit(EXIT_FAILURE);
}
if (pcap_datalink(handle) != DLT_EN10MB) {
fprintf(stderr, "%s is not an Ethernet\n", interface.c_str());
exit(EXIT_FAILURE);
}
if (!filter_exp.empty()) {
/* compile the filter expression */
if (pcap_compile(handle, &fp, filter_exp.c_str(), 0, net) == -1) {
fprintf(stderr, "Couldn't parse filter %s: %s\n",
filter_exp.c_str(), pcap_geterr(handle));
exit(EXIT_FAILURE);
}
/* apply the compiled filter */
if (pcap_setfilter(handle, &fp) == -1) {
fprintf(stderr, "Couldn't install filter %s: %s\n",
filter_exp.c_str(), pcap_geterr(handle));
exit(EXIT_FAILURE);
}
pcap_freecode(&fp);
}
/* start a separate thread */
running = true;
thread = std::thread([this]() {
if (pcap_loop(handle, num_packets, &PcapCapture::callback, reinterpret_cast<u_char*>(this)) < 0) {
//fprintf(stderr, "Error in pcap_loop: %s\n", pcap_geterr(handle));
//pcap_close(handle);
}
running = false;
});
}
void PcapCapture::stop() {
if (interfaces) {
pcap_freealldevs(interfaces);
}
if (handle) {
if (running == true) pcap_breakloop(handle);
pcap_close(handle);
//handle = nullptr;
}
if (thread.joinable()) {
thread.join();
}
/*if (!filter_exp.empty()) {
pcap_freecode(&fp);
}*/
}
/* print all available interfaces */
void PcapCapture::print_interfaces() {
int i = 0;
for (pcap_if_t *dev = interfaces; dev; dev = dev->next) {
printf("%d. %s ",++i, dev->name);
if (dev->description) {
printf("(%s)\n", dev->description);
}
else {
printf("\n");
}
}
}
/**
* @brief Static wrapper required by libpcap C API.
*
* Since libpcap expects a C-style function pointer,
* we forward the call to the current class instance.
*
* @param user Pointer to PcapCapture instance
* @param header Packet metadata
* @param packet Raw packet bytes
*/
void PcapCapture::callback(
u_char* user,
const struct pcap_pkthdr* header,
const u_char* packet
) {
auto* self = reinterpret_cast<PcapCapture*>(user);
if (!self->isRunning()) return;
self->got_packet(header, packet);
}
/**
* @brief Parses a single captured packet.
*
* Responsibilities:
* - Parse Ethernet header
* - Detect IP version (IPv4 / IPv6)
* - Extract transport & application protocols
* - Construct Packet abstraction
* - Forward packet to Stats engine
*
* Only IPv4 and IPv6 are currently processed.
* Other Ethernet types are ignored.
*/
void PcapCapture::got_packet(const struct pcap_pkthdr *header, const u_char *packet) {
if (!running) return;
// --- Ethernet header ---
const auto* ethernet = reinterpret_cast<const ether_header*>(packet);
uint16_t ether_type = ntohs(ethernet->ether_type);
Packet packetView;
/* if we have a ipv4 type */
if (ether_type == ETHERTYPE_IP) {
IPv4 ip(packet + sizeof(struct ether_header));
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());
stats->add_packet(packetView);
stats->push(packetView);
}
/* ipv6 type */
if (ether_type == ETHERTYPE_IPV6) {
IPv6 ip(packet + sizeof(struct ether_header));
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());
stats->add_packet(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) {
this->interface = interface;
this->num_packets = num_packets;
this->filter_exp = filter_exp;
this->stats = stats;
this->stats->set_packets_limit(packets_limit);
}
/**
* @brief Processes packets from an offline .pcap file.
*
* Differences from live mode:
* - Runs synchronously
* - No additional thread is created
* - Blocks until entire file is processed
*
* Used for post-capture analysis and exporting results.
*/
void PcapCapture::start_offline(std::string fpath) {
handle = pcap_open_offline(fpath.c_str(), errbuf);
if (handle == nullptr) {
fprintf(stderr, "Error opening offline file: %s\n", errbuf);
return;
}
running = true;
pcap_loop(handle, num_packets,
&PcapCapture::callback,
reinterpret_cast<u_char*>(this));
running = false;
}

View File

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

View File

@ -0,0 +1,77 @@
#include "../../include/cli/filter.hpp"
#include <map>
filter parse(std::string str) {
auto pos = str.find(':');
if (pos == std::string::npos) {
}
std::string type = str.substr(0, pos);
std::string value = str.substr(pos + 1);
if (type == "protocol") return {PROTOCOL, value};
if (type == "port") return {PORT, 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};
}
std::string get_bpf_filter(std::vector<filter>& f) {
std::map<filter_type, std::vector<std::string>> groups;
for (const auto& x : f) {
switch (x.type) {
case PROTOCOL:
if (x.val == "dns")
groups[PROTOCOL].push_back("port 53");
else
groups[PROTOCOL].push_back(x.val);
break;
case IP_DEST:
groups[IP_DEST].push_back("dst host " + x.val);
break;
case IP_SRC:
groups[IP_SRC].push_back("src host " + x.val);
break;
case PORT:
groups[PORT].push_back("port " + x.val);
break;
case IP_TYPE:
groups[IP_TYPE].push_back(x.val);
break;
default:
break;
}
}
std::string result;
bool first_group = true;
for (auto& [type, parts] : groups) {
if (!first_group)
result += " and ";
first_group = false;
if (parts.size() > 1)
result += "(";
for (size_t i = 0; i < parts.size(); ++i) {
result += parts[i];
if (i + 1 < parts.size())
result += " or ";
}
if (parts.size() > 1)
result += ")";
}
return result;
}

View File

@ -0,0 +1,211 @@
#include "../../include/packet/IP.hpp"
#include <cstdio>
#include <arpa/inet.h>
#include <netinet/icmp6.h>
#include <netinet/ip_icmp.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>
uint16_t IP_class::get_payload_len() {
return payload_len;
}
/*** Ipv4 ***/
IPv4::IPv4(const u_char* data) {
ip_hdr = reinterpret_cast<const ip*>(data);
ip_hdr_len = ip_hdr->ip_hl * 4;
if (ip_hdr_len < 20) {
fprintf(stderr, "Failed to initial IPv4 ");
}
}
TransportProtocol IPv4::get_protocol() {
switch (ip_hdr->ip_p) {
case IPPROTO_TCP:
handle_tcp();
return TransportProtocol::TCP;
case IPPROTO_UDP:
handle_udp();
return TransportProtocol::UDP;
case IPPROTO_ICMP:
handle_icmp();
return TransportProtocol::ICMP;
case IPPROTO_ICMPV6:
handle_icmpv6();
return TransportProtocol::ICMP6;
case IPPROTO_IGMP:
handle_igmp();
return TransportProtocol::IGMP;
default:
return TransportProtocol::UNKNOWN;
}
}
void IPv4::handle_tcp() {
auto* tcp = reinterpret_cast<tcphdr*>((u_char*)ip_hdr + ip_hdr_len);
src_port = ntohs(tcp->source);
dest_port = ntohs(tcp->dest);
payload_ptr = reinterpret_cast<u_char*>(tcp + tcp->doff * 4);
payload_len = ntohs(ip_hdr->ip_len) - (ip_hdr_len + tcp->doff * 4);
}
void IPv4::handle_udp() {
auto* udp = reinterpret_cast<udphdr*>((u_char*)ip_hdr + ip_hdr_len);
dest_port = ntohs(udp->dest);
src_port = ntohs(udp->source);
payload_ptr = (u_char*)udp + 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);
}
void IPv4::handle_icmpv6() {
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(const u_char* data) {
ip_hdr = reinterpret_cast<const ip6_hdr*>(data);
}
TransportProtocol IPv6::get_protocol() {
uint8_t hdr = ip_hdr->ip6_nxt;
ptr = reinterpret_cast<const uint8_t*>(ip_hdr + 1);
while (true) {
switch (hdr) {
case IPPROTO_TCP:
handle_tcp();
return TransportProtocol::TCP;
case IPPROTO_UDP:
handle_udp();
return TransportProtocol::UDP;
case IPPROTO_ICMP:
handle_icmp();
return TransportProtocol::ICMP;
case IPPROTO_ICMPV6:
handle_icmpv6();
return TransportProtocol::ICMP6;
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;
}
void IPv6::handle_tcp() {
const auto tcp = reinterpret_cast<const tcphdr*>(ptr);
dest_port = ntohs(tcp->dest);
src_port = ntohs(tcp->source);
payload_ptr = (const uint8_t*)tcp + tcp->doff * 4;
payload_len = ntohs(ip_hdr->ip6_plen) - tcp->doff * 4;
ptr = nullptr;
}
void IPv6::handle_udp() {
const auto udp = reinterpret_cast<const udphdr*>(ptr);
dest_port = ntohs(udp->dest);
src_port = ntohs(udp->source);
payload_ptr = (const uint8_t*)udp + sizeof(udphdr);
payload_len = ntohs(udp->len) - sizeof(udphdr);
ptr = nullptr;
}
void IPv6::handle_icmp() {
ptr = nullptr;
}
void IPv6::handle_icmpv6() {
payload_len = ntohs(ip_hdr->ip6_plen) - sizeof(icmp6_hdr);
ptr = nullptr;
}
void IPv6::handle_igmp() {
ptr = nullptr;
}
std::string IPv6::get_source() {
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;
}

View File

@ -0,0 +1,49 @@
#include "../../include/packet/packet.hpp"
#include <cstring>
ApplicationProtocol Packet::get_application_protocol() {
if (!payload_ptr || payload_len < 4) goto check_port;
if (transport_protocol == TransportProtocol::TCP) {
if (!memcmp(payload_ptr, "GET ", 4) ||
!memcmp(payload_ptr, "POST", 4) ||
!memcmp(payload_ptr, "HEAD", 4) ||
!memcmp(payload_ptr, "PUT ", 4) ||
!memcmp(payload_ptr, "HTTP", 4))
return ApplicationProtocol::HTTP;
}
if ((src_port == 53 || dst_port == 53) && payload_len >= 12) {
return ApplicationProtocol::DNS;
}
if (transport_protocol == TransportProtocol::TCP && payload_len >= 3) {
if (payload_ptr[0] == 0x16 && payload_ptr[1] == 0x03)
return ApplicationProtocol::HTTPS;
}
check_port:
uint16_t port = (src_port < dst_port) ? src_port : dst_port;
if (transport_protocol == TransportProtocol::TCP) {
switch (port) {
case 21: return ApplicationProtocol::FTP;
case 22: return ApplicationProtocol::SSH;
case 25: return ApplicationProtocol::SMTP;
case 53: return ApplicationProtocol::DNS;
case 80: return ApplicationProtocol::HTTP;
case 443: return ApplicationProtocol::HTTPS;
default: return ApplicationProtocol::UNKNOWN;
}
}
if (transport_protocol == TransportProtocol::UDP) {
switch (port) {
case 53: return ApplicationProtocol::DNS;
case 443: return ApplicationProtocol::QUIC;
case 123: return ApplicationProtocol::NTP;
default: return ApplicationProtocol::UNKNOWN;
}
}
return ApplicationProtocol::UNKNOWN;
}

View File

@ -0,0 +1,421 @@
#include "../../include/stats/protocolStats.hpp"
#include "ftxui/dom/table.hpp"
Stats::Stats() {
last_tick = std::chrono::steady_clock::now();
}
/**
* @brief Aggregates a newly captured packet.
*
* Updates:
* - Total packet/byte counters
* - Transport protocol stats
* - Application protocol stats
* - IP-level statistics
* - Communication pairs
*
* Must be called only from capture thread.
* Protected by mutex.
*/
void Stats::add_packet(Packet &packet) {
std::lock_guard<std::mutex> lock(mtx);
++snapshot.total_p;
snapshot.total_b += packet.total_len;
auto& t = transport_map[packet.transport_protocol];
t.packets++;
t.bytes += packet.total_len;
auto& a = application_map[packet.application_protocol];
a.packets++;
a.bytes += packet.payload_len;
ip_map[packet.src].packets_sent++;
ip_map[packet.src].bytes_sent += packet.total_len;
ip_map[packet.dst].packets_received++;
ip_map[packet.dst].bytes_received += packet.total_len;
auto key = std::make_pair(packet.src, packet.dst);
pairs[key].packets++;
pairs[key].bytes += packet.total_len;
}
const char* transport_to_str(TransportProtocol p) {
switch (p) {
case TransportProtocol::TCP: return "TCP";
case TransportProtocol::UDP: return "UDP";
case TransportProtocol::ICMP: return "ICMP";
case TransportProtocol::ICMP6: return "ICMP6";
case TransportProtocol::IGMP: return "IGMP";
default: return "UNKNOWN";
}
}
const char* app_to_str(ApplicationProtocol p) {
switch (p) {
case ApplicationProtocol::HTTP: return "HTTP";
case ApplicationProtocol::HTTPS: return "HTTPS";
case ApplicationProtocol::DNS: return "DNS";
case ApplicationProtocol::FTP: return "FTP";
case ApplicationProtocol::SSH: return "SSH";
case ApplicationProtocol::SMTP: return "SMTP";
case ApplicationProtocol::QUIC: return "QUIC";
case ApplicationProtocol::NTP: return "NTP";
default: return "UNKNOWN";
}
}
/**
* @brief Rebuilds transport protocol snapshot table.
*
* Sorts protocols by packet count (descending).
* Calculates percentage relative to total traffic.
*
* Called periodically by UI update thread.
*/
void Stats::update_transport_stats() {
std::lock_guard<std::mutex> lock(mtx);
snapshot.transport_rows.clear();
snapshot.transport_rows.push_back({ "Proto", "Packets", "Bytes", "%" });
std::vector<std::pair<TransportProtocol, protocolStats>> tps(
transport_map.begin(), transport_map.end()
);
std::sort(tps.begin(), tps.end(),
[](auto& a, auto& b) {
return a.second.packets > b.second.packets;
});
for (const auto& [proto, stats] : tps) {
double percent = snapshot.total_b ? stats.bytes * 100.0 / snapshot.total_b : 0.0;
snapshot.transport_rows.push_back({
transport_to_str(proto),
std::to_string(stats.packets),
std::format("{:.2f}", stats.bytes / (1024.0 * 1024.0)),
std::format("{:.2f}", percent)
});
}
}
/**
* @brief Rebuilds application protocol snapshot.
*
* Sorted by packet count.
* Percent calculated relative to total bytes.
*/
void Stats::update_application_stats() {
std::lock_guard<std::mutex> lock(mtx);
std::vector<std::pair<ApplicationProtocol, protocolStats>> apps(
application_map.begin(), application_map.end()
);
std::sort(apps.begin(), apps.end(),
[](auto& a, auto& b) {
return a.second.packets > b.second.packets;
});
snapshot.app_rows.clear();
snapshot.app_rows.push_back({ "Proto", "Packets", "Bytes (MB)", "%" });
for (const auto& [proto, s] : apps) {
double percent = snapshot.total_b
? s.bytes * 100.0 / snapshot.total_b
: 0.0;
snapshot.app_rows.push_back({
app_to_str(proto),
std::to_string(s.packets),
std::format("{:.2f}", s.bytes / (1024.0 * 1024.0)),
std::format("{:.2f}", percent)
});
}
}
/**
* @brief Generates snapshot for top IP addresses.
*
* @param limit Maximum number of IPs to display.
*
* Sorted by transmitted packets (descending).
*/
void Stats::update_ip_stats(size_t limit) {
std::lock_guard<std::mutex> lock(mtx);
snapshot.rows.clear();
snapshot.rows.push_back({"IP Address", "Packets TX", "Packets RX"});
std::vector<std::pair<std::string, IPStats>> ips(
ip_map.begin(), ip_map.end()
);
std::sort(ips.begin(), ips.end(),
[](auto& a, auto& b) {
return a.second.packets_sent > b.second.packets_sent;
});
size_t count = 0;
for (const auto& [ip, s] : ips) {
if (count++ >= limit) break;
snapshot.rows.push_back({ip,
"TX: " + std::to_string(s.packets_sent),
"RX: " + std::to_string(s.packets_received)}
);
}
}
/**
* @brief Builds snapshot of top communication pairs.
*
* @param limit Maximum number of pairs to include.
*
* Sorted by total bytes transferred.
*/
void Stats::update_pairs(size_t limit) {
std::lock_guard<std::mutex> lock(mtx);
std::vector<std::pair<std::pair<std::string, std::string>, protocolStats>> vec(pairs.begin(), pairs.end());
std::sort(vec.begin(), vec.end(),
[](auto& a, auto& b) {
return a.second.bytes > b.second.bytes;
});
snapshot.pairs_rows.clear();
snapshot.pairs_rows.push_back({ "Source", "Destination", "bytes received", "%" });
size_t count = 0;
for (const auto& [pair, s] : vec) {
if (count++ >= limit) break;
double percent = snapshot.total_b ? (s.bytes * 100.0 / snapshot.total_b) : 0.0;
snapshot.pairs_rows.push_back({
pair.first,
pair.second,
std::format("{:.0f}", s.bytes * 1.0),
std::format("{:.2f}", percent),
});
}
}
void Stats::update_packets(){
std::lock_guard lock(mtx);
snapshot.packets_rows.clear();
snapshot.packets_rows.push_back({ "IPVersion", "Transport protocol", "Source", "Destination", "App protocol"});
for (auto& packet : packets) {
snapshot.packets_rows.push_back({
packet.ip_version == IPVersion::v4 ? "IPv4" : "IPv6",
transport_to_str(packet.transport_protocol),
packet.src,
packet.dst,
app_to_str(packet.application_protocol),
});
}
}
double Stats::smooth_value(size_t i, size_t start) {
const int window = 3;
double sum = 0.0;
int count = 0;
for (int k = -window; k <= window; ++k) {
long idx = (long)i + k;
if (idx >= (long)start && idx < (long)snapshot.bandwidth_history.size()) {
sum += snapshot.bandwidth_history[idx].bytes_per_sec;
count++;
}
}
return count ? sum / count : 0.0;
}
/**
* @brief Calculates current bandwidth (bytes/sec).
*
* Uses:
* - Delta bytes since last tick
* - Time elapsed
* - Exponential smoothing to reduce noise
*
* Stores history for graph rendering.
*/
void Stats::update_bandwidth() {
std::lock_guard<std::mutex> lock(mtx);
using namespace std::chrono;
auto now = steady_clock::now();
double ts = duration_cast<duration<double>>(now.time_since_epoch()).count();
double elapsed = duration_cast<duration<double>>(now - last_tick).count();
if (elapsed >= 1.0) {
uint32_t delta_bytes = snapshot.total_b - last_b;
snapshot.bandwidth = delta_bytes / elapsed; // bytes per second
last_b = snapshot.total_b;
last_tick = now;
const double alpha = 0.2;
smooth_bandwidth =
alpha * snapshot.bandwidth + (1.0 - alpha) * smooth_bandwidth;
snapshot.bandwidth_history.push_back({ ts, smooth_bandwidth });
}
snapshot.max_bandwidth = std::max(snapshot.max_bandwidth, snapshot.bandwidth);
}
/**
* @brief Exports current statistics to CSV file.
*
* Includes:
* - Summary
* - Transport protocols
* - Application protocols
* - IP statistics
* - Bandwidth history
*/
void Stats::export_csv(const std::string& filename) {
std::lock_guard<std::mutex> lock(mtx);
std::ofstream file(filename);
if (!file.is_open()) return;
file << "summary\n";
file << "total_packets,total_bytes,bandwidth\n";
file << snapshot.total_p << ","
<< snapshot.total_b << ","
<< snapshot.bandwidth << "\n\n";
// ===== Transport protocols =====
file << "transport_protocols\n";
file << "protocol,packets,bytes,percent\n";
for (const auto& [proto, s] : transport_map) {
double percent = snapshot.total_b ? (s.bytes * 100.0 / snapshot.total_b) : 0.0;
file << transport_to_str(proto) << ","
<< s.packets << ","
<< s.bytes << ","
<< percent << "\n";
}
file << "\n";
// ===== Application protocols =====
file << "application_protocols\n";
file << "protocol,packets,payload_bytes\n";
for (const auto& [proto, s] : application_map) {
file << static_cast<int>(proto) << ","
<< s.packets << ","
<< s.bytes << "\n";
}
file << "\n";
// ===== IP stats =====
file << "ip_stats\n";
file << "ip,packets_sent,packets_received,bytes_sent,bytes_received\n";
for (const auto& [ip, s] : ip_map) {
file << ip << ","
<< s.packets_sent << ","
<< s.packets_received << ","
<< s.bytes_sent << ","
<< s.bytes_received << "\n";
}
//bandwidth
file << "time,bandwidth\n";
for (const auto& p : snapshot.bandwidth_history) {
file << p.timestamp << "," << p.bytes_per_sec << "\n";
}
file.close();
}
/**
* @brief Exports statistics to JSON format.
*
* Designed for:
* - External processing
* - Visualization tools
* - Data pipelines
*/
void Stats::export_json(const std::string& filename) {
std::lock_guard<std::mutex> lock(mtx);
std::ofstream file(filename);
if (!file.is_open()) return;
file << "{\n";
// ===== Summary =====
file << " \"summary\": {\n";
file << " \"total_packets\": " << snapshot.total_p << ",\n";
file << " \"total_bytes\": " << snapshot.total_b << ",\n";
file << " \"bandwidth\": " << snapshot.bandwidth << "\n";
file << " },\n";
// ===== Transport =====
file << " \"transport\": [\n";
bool first = true;
for (const auto& [proto, s] : transport_map) {
if (!first) file << ",\n";
first = false;
double percent = snapshot.total_b ? (s.bytes * 100.0 / snapshot.total_b) : 0.0;
file << " {\n";
file << " \"protocol\": \"" << transport_to_str(proto) << "\",\n";
file << " \"packets\": " << s.packets << ",\n";
file << " \"bytes\": " << s.bytes << ",\n";
file << " \"percent\": " << percent << "\n";
file << " }";
}
file << "\n ],\n";
// ===== IP stats =====
file << " \"top_ips\": [\n";
first = true;
for (const auto& [ip, s] : ip_map) {
if (!first) file << ",\n";
first = false;
file << " {\n";
file << " \"ip\": \"" << ip << "\",\n";
file << " \"packets_sent\": " << s.packets_sent << ",\n";
file << " \"packets_received\": " << s.packets_received << ",\n";
file << " \"bytes_sent\": " << s.bytes_sent << ",\n";
file << " \"bytes_received\": " << s.bytes_received << "\n";
file << " }";
}
file << "\n ]\n";
file << ",\n \"communication_pairs\": [\n";
first = true;
for (const auto& [pair, s] : pairs) {
if (!first) file << ",\n";
first = false;
file << " {\n";
file << " \"src\": \"" << pair.first << "\",\n";
file << " \"dst\": \"" << pair.second << "\",\n";
file << " \"packets\": " << s.packets << ",\n";
file << " \"bytes\": " << s.bytes << "\n";
file << " }";
}
file << "\n ]";
file << "}\n";
file.close();
}