fix bugs, data races, add: install bash script, just cfg, CMakePresets
This commit is contained in:
parent
d67499febf
commit
1ade9d6dc2
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -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/
|
||||
|
|
@ -26,6 +26,10 @@ Or grant capabilities:
|
|||
sudo setcap cap_net_raw,cap_net_admin=eip ./network-traffic-analyzer
|
||||
```
|
||||
|
||||
Or you can use `just` command
|
||||
```
|
||||
just run
|
||||
```
|
||||
---
|
||||
|
||||
# Features
|
||||
|
|
@ -59,29 +63,28 @@ sudo setcap cap_net_raw,cap_net_admin=eip ./network-traffic-analyzer
|
|||
- FTXUI
|
||||
- CMake
|
||||
|
||||
# Build
|
||||
```
|
||||
mkdir build && cd build
|
||||
cmake ..
|
||||
make
|
||||
# Setup
|
||||
## 1. clone the repo then
|
||||
```bash
|
||||
cd network-traffic-analyzer
|
||||
./install.sh
|
||||
```
|
||||
|
||||
# Usage Example
|
||||
|
||||
### Live capture on eth0
|
||||
```
|
||||
sudo ./network-traffic-analyzer -i eth0
|
||||
just capture -i eth0
|
||||
```
|
||||
### Capture 100 packets
|
||||
```
|
||||
sudo ./network-traffic-analyzer -i wlan0 -c 100
|
||||
just run -i wlan0 -c 100
|
||||
```
|
||||
### Analyze offline pcap file
|
||||
```
|
||||
sudo ./network-traffic-analyzer --offline traffic.pcap
|
||||
just run --offline traffic.pcap
|
||||
```
|
||||
### Export results (json / csv)
|
||||
```
|
||||
sudo ./network-traffic-analyzer --json result.json --csv result.csv
|
||||
|
||||
just run --json result.json --csv result.csv
|
||||
```
|
||||
|
|
|
|||
|
|
@ -4,37 +4,22 @@
|
|||
#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
|
||||
);
|
||||
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);
|
||||
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_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
|
||||
);
|
||||
ftxui::Element render_footer(bool capture_finished, std::chrono::seconds timer);
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
#endif //VIEW_HPP
|
||||
#endif // VIEW_HPP
|
||||
|
|
|
|||
|
|
@ -1,28 +1,25 @@
|
|||
#ifndef PCAPCAPTURE_HPP
|
||||
#define PCAPCAPTURE_HPP
|
||||
|
||||
#include <memory>
|
||||
#include <queue>
|
||||
#include <pcap/pcap.h>
|
||||
#include <thread>
|
||||
#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/tcp.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
|
||||
}
|
||||
|
||||
#include "../packet/IP.hpp"
|
||||
#include "../../include/stats/protocolStats.hpp"
|
||||
#include "../packet/IP.hpp"
|
||||
#include "../packet/packet.hpp"
|
||||
|
||||
/**
|
||||
|
|
@ -43,7 +40,7 @@ extern "C" {
|
|||
*/
|
||||
|
||||
class PcapCapture {
|
||||
private:
|
||||
private:
|
||||
/* libpcap error buffer */
|
||||
char errbuf[PCAP_ERRBUF_SIZE];
|
||||
|
||||
|
|
@ -52,7 +49,10 @@ private:
|
|||
/* compiled filter program (expression) */
|
||||
struct bpf_program fp = {};
|
||||
/* 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 */
|
||||
bpf_u_int32 mask = 0;
|
||||
|
|
@ -72,7 +72,7 @@ private:
|
|||
* 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);
|
||||
|
|
@ -80,28 +80,23 @@ private:
|
|||
/* Separate thread used for live capture */
|
||||
std::thread thread;
|
||||
std::atomic<bool> running{false};
|
||||
void stop();
|
||||
Stats *stats;
|
||||
|
||||
public:
|
||||
|
||||
public:
|
||||
~PcapCapture();
|
||||
void print_interfaces();
|
||||
|
||||
bool isRunning() {
|
||||
return running;
|
||||
}
|
||||
void setRunning(bool running) {
|
||||
this->running = running;
|
||||
}
|
||||
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 set_capabilities(const std::string &interface, int num_packets, const std::string &filter_exp,
|
||||
int packets_limit, Stats *stats);
|
||||
void initialize();
|
||||
|
||||
|
||||
void start();
|
||||
void start_offline(std::string fpath);
|
||||
void stop();
|
||||
void start_offline(const std::string &fpath);
|
||||
};
|
||||
|
||||
|
||||
#endif //PCAPCAPTURE_HPP
|
||||
#endif // PCAPCAPTURE_HPP
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ struct argsParser {
|
|||
po::variables_map vm;
|
||||
void print_help() const;
|
||||
|
||||
argsParser(int argc, char** argv);
|
||||
argsParser(int argc, char **argv);
|
||||
};
|
||||
|
||||
#endif //ARGSPARSE_HPP
|
||||
#endif // ARGSPARSE_HPP
|
||||
|
|
|
|||
|
|
@ -3,24 +3,14 @@
|
|||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
enum filter_type {
|
||||
PROTOCOL,
|
||||
PORT,
|
||||
IP_TYPE,
|
||||
IP_SRC,
|
||||
IP_DEST,
|
||||
NONE
|
||||
};
|
||||
enum filter_type { PROTOCOL, PORT, IP_TYPE, IP_SRC, IP_DEST, NONE };
|
||||
|
||||
struct filter {
|
||||
filter_type type;
|
||||
std::string val;
|
||||
};
|
||||
|
||||
filter parse(const std::string &str);
|
||||
std::string get_bpf_filter(const std::vector<filter> &f);
|
||||
|
||||
filter parse(std::string str);
|
||||
std::string get_bpf_filter(std::vector<filter>& f);
|
||||
|
||||
|
||||
|
||||
#endif //FILTER_HPP
|
||||
#endif // FILTER_HPP
|
||||
|
|
|
|||
|
|
@ -1,72 +1,75 @@
|
|||
#ifndef IP_HPP
|
||||
#define IP_HPP
|
||||
#include <string>
|
||||
#include <netinet/ip.h>
|
||||
#include <netinet/igmp.h>
|
||||
#include <netinet/ip6.h>
|
||||
#include "packet.hpp"
|
||||
#include <netinet/igmp.h>
|
||||
#include <netinet/ip.h>
|
||||
#include <netinet/ip6.h>
|
||||
#include <string>
|
||||
|
||||
/* virtual class for our IPv4, IPv6 classes */
|
||||
class IP_class {
|
||||
protected:
|
||||
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;
|
||||
TransportProtocol protocol = TransportProtocol::UNKNOWN;
|
||||
std::string src;
|
||||
std::string dst;
|
||||
|
||||
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_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* get_payload_ptr() const { return payload_ptr; }
|
||||
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;
|
||||
private:
|
||||
const ip *ip_hdr = nullptr;
|
||||
int ip_hdr_len = 0;
|
||||
uint16_t src_port = 0;
|
||||
uint16_t dest_port = 0;
|
||||
protected:
|
||||
|
||||
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;
|
||||
public:
|
||||
uint16_t get_src_port() override;
|
||||
uint16_t get_dest_port() override;
|
||||
|
||||
explicit IPv4(const u_char* data);
|
||||
explicit IPv4(const u_char *data);
|
||||
};
|
||||
|
||||
|
||||
|
||||
/*** ipv6 ***/
|
||||
class IPv6 : public IP_class{
|
||||
protected:
|
||||
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;
|
||||
|
||||
private:
|
||||
const ip6_hdr *ip_hdr = nullptr;
|
||||
int ip_hdr_len = 40;
|
||||
|
||||
in6_addr ip_source;
|
||||
|
|
@ -75,17 +78,13 @@ private:
|
|||
uint16_t src_port;
|
||||
uint16_t dest_port;
|
||||
|
||||
const uint8_t* ptr = nullptr;
|
||||
const uint8_t *ptr = nullptr;
|
||||
|
||||
public:
|
||||
explicit IPv6(const u_char* data);
|
||||
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
|
||||
#endif // IP_HPP
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
#ifndef PACKET_HPP
|
||||
#define PACKET_HPP
|
||||
#include <cstdint>
|
||||
#include <utility>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
enum IPVersion {
|
||||
v4, v6,
|
||||
v4,
|
||||
v6,
|
||||
};
|
||||
|
||||
enum class TransportProtocol {
|
||||
|
|
@ -32,9 +33,9 @@ struct Packet {
|
|||
IPVersion ip_version;
|
||||
TransportProtocol transport_protocol;
|
||||
ApplicationProtocol application_protocol;
|
||||
//src address
|
||||
// src address
|
||||
std::string src;
|
||||
//dest address
|
||||
// dest address
|
||||
std::string dst;
|
||||
uint16_t src_port;
|
||||
uint16_t dst_port;
|
||||
|
|
@ -42,18 +43,17 @@ struct Packet {
|
|||
uint32_t total_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,
|
||||
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(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) {
|
||||
application_protocol = get_application_protocol();
|
||||
this->payload_ptr = nullptr;
|
||||
}
|
||||
Packet() { }
|
||||
|
||||
private:
|
||||
private:
|
||||
ApplicationProtocol get_application_protocol();
|
||||
};
|
||||
#endif //PACKET_HPP
|
||||
#endif // PACKET_HPP
|
||||
|
|
|
|||
|
|
@ -1,17 +1,14 @@
|
|||
#ifndef PROTOCOLSTATS_HPP
|
||||
#define PROTOCOLSTATS_HPP
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include "../packet/packet.hpp"
|
||||
#include <unordered_map>
|
||||
#include "ftxui/dom/elements.hpp"
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <queue>
|
||||
#include "ftxui/dom/elements.hpp"
|
||||
#include <unordered_map>
|
||||
|
||||
using namespace ftxui;
|
||||
struct protocolStats {
|
||||
uint32_t packets = 0;
|
||||
uint32_t bytes = 0;
|
||||
|
|
@ -43,14 +40,12 @@ struct StatsSnapshot {
|
|||
std::vector<std::vector<std::string>> packets_rows;
|
||||
|
||||
uint32_t total_p = 0, total_b = 0;
|
||||
//bandwidth
|
||||
// bandwidth
|
||||
std::vector<BandwidthPoint> bandwidth_history;
|
||||
double bandwidth = 0;
|
||||
mutable double max_bandwidth = 0;
|
||||
|
||||
double max_bandwidth = 0;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Thread-safe statistics engine.
|
||||
*
|
||||
|
|
@ -64,14 +59,13 @@ struct StatsSnapshot {
|
|||
* All write operations are protected by mutex.
|
||||
*/
|
||||
class Stats {
|
||||
private:
|
||||
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;
|
||||
|
|
@ -81,10 +75,10 @@ private:
|
|||
std::deque<Packet> packets;
|
||||
int limit_packets = 10;
|
||||
|
||||
StatsSnapshot snapshot;
|
||||
|
||||
public:
|
||||
|
||||
void push(const Packet& p) {
|
||||
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();
|
||||
|
|
@ -92,16 +86,17 @@ public:
|
|||
packets.push_back(p);
|
||||
}
|
||||
|
||||
StatsSnapshot snapshot;
|
||||
StatsSnapshot get_snapshot() {
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
return 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 set_packets_limit(int limit) { limit_packets = limit; }
|
||||
|
||||
void add_packet(Packet &packet);
|
||||
void add_packet(const Packet &packet);
|
||||
|
||||
void update_transport_stats();
|
||||
void update_application_stats();
|
||||
|
|
@ -109,12 +104,10 @@ public:
|
|||
void update_pairs(size_t limit = 10);
|
||||
void update_packets();
|
||||
|
||||
void export_csv(const std::string& filename);
|
||||
void export_json(const std::string& filename);
|
||||
void export_csv(const std::string &filename);
|
||||
void export_json(const std::string &filename);
|
||||
|
||||
Stats();
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif //PROTOCOLSTATS_HPP
|
||||
#endif // PROTOCOLSTATS_HPP
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -1,26 +1,22 @@
|
|||
#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 <boost/program_options.hpp>
|
||||
#include <ftxui/component/component.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"
|
||||
#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 */
|
||||
PcapCapture capture;
|
||||
capture.initialize();
|
||||
|
||||
/* initialize stats */
|
||||
Stats stats;
|
||||
|
||||
/* initializing the command line parser */
|
||||
argsParser parser(argc, argv);
|
||||
|
||||
|
|
@ -43,8 +39,8 @@ int main(int argc, char **argv)
|
|||
/* 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) {
|
||||
auto &f = parser.vm["filter"].as<std::vector<std::string>>();
|
||||
for (auto &x : f) {
|
||||
filters.push_back(parse(x));
|
||||
filterString += x + " ";
|
||||
}
|
||||
|
|
@ -57,97 +53,102 @@ int main(int argc, char **argv)
|
|||
/* set the flags to capture engine */
|
||||
capture.set_capabilities(interface, count, expression, limit, &stats);
|
||||
|
||||
|
||||
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 (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 */
|
||||
stats.update_packets();
|
||||
stats.update_application_stats();
|
||||
stats.update_transport_stats();
|
||||
stats.update_ip_stats(10);
|
||||
stats.update_pairs();
|
||||
stats.update_bandwidth();
|
||||
}
|
||||
/* 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();
|
||||
}
|
||||
else {
|
||||
capture.start();
|
||||
}
|
||||
|
||||
/* UI */
|
||||
using namespace ftxui;
|
||||
auto screen = ScreenInteractive::Fullscreen();
|
||||
/* our class for UI */
|
||||
View view;
|
||||
|
||||
/* timer */
|
||||
/* UI */
|
||||
// Timer
|
||||
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([&] {
|
||||
auto snapshot = stats.snapshot;
|
||||
return view.render(snapshot,
|
||||
interface,
|
||||
filterString,
|
||||
capture_finished,
|
||||
timer);
|
||||
});
|
||||
/* our class for UI */
|
||||
View view;
|
||||
std::mutex render_mtx;
|
||||
std::mutex event_mtx;
|
||||
ftxui::Element current_render = isOffline
|
||||
? view.render(stats.get_snapshot(), interface, filterString, true, timer.load())
|
||||
: ftxui::text("Starting capture...");
|
||||
|
||||
auto component = CatchEvent(renderer, [&](Event e) {
|
||||
if (e == Event::Character('q') || e == Event::Escape) {
|
||||
capture.stop();
|
||||
screen.Exit();
|
||||
}
|
||||
return true;
|
||||
});
|
||||
std::mutex screen_mtx;
|
||||
|
||||
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) {
|
||||
updater = std::thread([&] {
|
||||
while (capture.isRunning()) {
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
timer.store(std::chrono::duration_cast<std::chrono::seconds>(now - begin));
|
||||
|
||||
stats.update_packets();
|
||||
stats.update_application_stats();
|
||||
stats.update_transport_stats();
|
||||
stats.update_ip_stats(10);
|
||||
stats.update_pairs();
|
||||
stats.update_bandwidth();
|
||||
if (timer.load() >= std::chrono::seconds(time) || !capture.isRunning()) {
|
||||
capture_finished = true;
|
||||
}
|
||||
|
||||
screen.PostEvent(Event::Custom);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(300));
|
||||
stats.update_packets();
|
||||
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();
|
||||
timer = std::chrono::duration_cast<std::chrono::seconds>(
|
||||
current_time - begin);
|
||||
ftxui::Element new_frame =
|
||||
view.render(stats.get_snapshot(), interface, filterString, capture_finished, timer.load());
|
||||
{
|
||||
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))
|
||||
break;
|
||||
}
|
||||
// std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
capture_finished = true;
|
||||
screen.PostEvent(Event::Custom);
|
||||
});
|
||||
}
|
||||
screen.Loop(component);
|
||||
|
||||
screen.Loop(component);
|
||||
ui_running = false;
|
||||
capture_finished = true;
|
||||
|
||||
if (updater.joinable())
|
||||
updater.join();
|
||||
if (application_thread.joinable())
|
||||
application_thread.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>());
|
||||
}
|
||||
// Export stats if needed
|
||||
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;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,53 +1,39 @@
|
|||
#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) {
|
||||
using namespace ftxui;
|
||||
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 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,
|
||||
auto ip_section = hbox({render_ip(data) | border | size(HEIGHT, LESS_THAN, 10) | frame | vscroll_indicator,
|
||||
|
||||
render_bandwidth(data)
|
||||
| border
|
||||
| flex
|
||||
});
|
||||
render_bandwidth(data) | border | flex});
|
||||
|
||||
auto left_panel =
|
||||
vbox({
|
||||
transport_section,
|
||||
separator(),
|
||||
ip_section,
|
||||
}) | flex_grow;
|
||||
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 right_panel = render_packets(data) | border | size(WIDTH, EQUAL, 100) | frame | vscroll_indicator;
|
||||
|
||||
auto body =
|
||||
hbox({
|
||||
left_panel,
|
||||
separator(),
|
||||
right_panel,
|
||||
}) | flex;
|
||||
auto body = hbox({
|
||||
left_panel,
|
||||
separator(),
|
||||
right_panel,
|
||||
}) |
|
||||
flex;
|
||||
|
||||
auto footer = render_footer(capture_finished, timer);
|
||||
|
||||
|
|
@ -68,41 +54,33 @@ ftxui::Element View::render(const StatsSnapshot& data, const std::string& interf
|
|||
* - Active filter
|
||||
* - 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({
|
||||
vbox({
|
||||
text("Network Traffic Analyzer") | bold,
|
||||
text("Interface: " + interface),
|
||||
text("Filter: " + filter),
|
||||
}) | flex,
|
||||
separator(),
|
||||
render_stats(data) | flex,
|
||||
}) | border;
|
||||
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_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)
|
||||
});
|
||||
|
||||
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) {
|
||||
ftxui::Element View::render_transport(const StatsSnapshot &data) {
|
||||
Table table(data.transport_rows);
|
||||
table.SelectAll().Border(LIGHT);
|
||||
|
||||
|
|
@ -111,12 +89,9 @@ ftxui::Element View::render_transport(const StatsSnapshot& data) {
|
|||
table.SelectRow(0).Decorate(bold);
|
||||
table.SelectRow(0).Border(DOUBLE);
|
||||
|
||||
return vbox({
|
||||
text("=== Transport protocols === ") | bold,
|
||||
table.Render()
|
||||
}) | flex;
|
||||
return vbox({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.SelectAll().Border(LIGHT);
|
||||
|
||||
|
|
@ -125,12 +100,9 @@ ftxui::Element View::render_application(const StatsSnapshot& data) {
|
|||
table.SelectRow(0).Decorate(bold);
|
||||
table.SelectRow(0).Border(DOUBLE);
|
||||
|
||||
return vbox({
|
||||
text("=== Application protocols ===") | bold,
|
||||
table.Render()
|
||||
}) | flex;
|
||||
return vbox({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.SelectAll().Border(LIGHT);
|
||||
|
|
@ -140,13 +112,12 @@ ftxui::Element View::render_ip(const StatsSnapshot& data) {
|
|||
table.SelectRow(0).Decorate(bold);
|
||||
table.SelectRow(0).Border(DOUBLE);
|
||||
|
||||
return vbox({
|
||||
text("=== Top IP addresses ===") | bold,
|
||||
return vbox({text("=== Top IP addresses ===") | bold,
|
||||
|
||||
table.Render()
|
||||
}) | flex;
|
||||
table.Render()}) |
|
||||
flex;
|
||||
}
|
||||
ftxui::Element View::render_pairs(const StatsSnapshot& data) {
|
||||
ftxui::Element View::render_pairs(const StatsSnapshot &data) {
|
||||
Table table(data.pairs_rows);
|
||||
table.SelectAll().Border(LIGHT);
|
||||
|
||||
|
|
@ -155,10 +126,7 @@ ftxui::Element View::render_pairs(const StatsSnapshot& data) {
|
|||
table.SelectRow(0).Decorate(bold);
|
||||
table.SelectRow(0).Border(DOUBLE);
|
||||
|
||||
return vbox({
|
||||
text("=== Top communication pairs ===") | bold,
|
||||
table.Render()
|
||||
}) | flex;
|
||||
return vbox({text("=== Top communication pairs ===") | bold, table.Render()}) | flex;
|
||||
}
|
||||
/**
|
||||
* @brief Renders bandwidth graph.
|
||||
|
|
@ -167,8 +135,7 @@ ftxui::Element View::render_pairs(const StatsSnapshot& data) {
|
|||
* Scales dynamically based on max bandwidth.
|
||||
*/
|
||||
|
||||
ftxui::Element View::render_bandwidth(const StatsSnapshot& data) {
|
||||
using namespace ftxui;
|
||||
ftxui::Element View::render_bandwidth(const StatsSnapshot &data) {
|
||||
|
||||
GraphFunction fn = [this, data](int width, int height) {
|
||||
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 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);
|
||||
|
|
@ -188,16 +154,13 @@ ftxui::Element View::render_bandwidth(const StatsSnapshot& data) {
|
|||
|
||||
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 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));
|
||||
|
|
@ -207,13 +170,10 @@ ftxui::Element View::render_bandwidth(const StatsSnapshot& data) {
|
|||
};
|
||||
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),
|
||||
}) ;
|
||||
graph(fn) | 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.SelectAll().Border(LIGHT);
|
||||
|
||||
|
|
@ -221,9 +181,8 @@ ftxui::Element View::render_packets(const StatsSnapshot& data) {
|
|||
table.SelectRow(0).SeparatorVertical(LIGHT);
|
||||
table.SelectRow(0).Decorate(bold);
|
||||
table.SelectRow(0).Border(DOUBLE);
|
||||
return vbox({
|
||||
text("=== Packets ===") | bold,
|
||||
return vbox({text("=== Packets ===") | bold,
|
||||
|
||||
table.Render() | flex
|
||||
}) | flex;
|
||||
table.Render() | flex}) |
|
||||
flex;
|
||||
}
|
||||
|
|
@ -1,102 +1,120 @@
|
|||
#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);
|
||||
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.
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
* 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);
|
||||
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);
|
||||
handle.reset(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);
|
||||
throw std::runtime_error("Couldn't open device " + interface + ": " + errbuf);
|
||||
}
|
||||
|
||||
if (pcap_datalink(handle) != DLT_EN10MB) {
|
||||
fprintf(stderr, "%s is not an Ethernet\n", interface.c_str());
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
datalink_type(pcap_datalink(handle.get()));
|
||||
|
||||
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);
|
||||
if (pcap_compile(handle.get(), &fp, filter_exp.c_str(), 0, net) == -1) {
|
||||
throw std::runtime_error("Couldn't parse filter " + filter_exp + ": " + pcap_geterr(handle.get()));
|
||||
}
|
||||
|
||||
|
||||
/* 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);
|
||||
if (pcap_setfilter(handle.get(), &fp) == -1) {
|
||||
throw std::runtime_error("Couldn't install filter " + filter_exp + ": " + pcap_geterr(handle.get()));
|
||||
}
|
||||
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);
|
||||
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));
|
||||
// pcap_close(handle);
|
||||
// throw std::runtime_error("Couldn't start capture");
|
||||
}
|
||||
running = false;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
PcapCapture::~PcapCapture() { stop(); }
|
||||
void PcapCapture::stop() {
|
||||
if (interfaces) {
|
||||
pcap_freealldevs(interfaces);
|
||||
}
|
||||
if (handle) {
|
||||
if (running == true) pcap_breakloop(handle);
|
||||
pcap_close(handle);
|
||||
//handle = nullptr;
|
||||
}
|
||||
pcap_freecode(&fp);
|
||||
if (!handle)
|
||||
return;
|
||||
|
||||
if (thread.joinable()) {
|
||||
running = false;
|
||||
|
||||
pcap_breakloop(handle.get());
|
||||
|
||||
if (thread.joinable())
|
||||
thread.join();
|
||||
}
|
||||
/*if (!filter_exp.empty()) {
|
||||
pcap_freecode(&fp);
|
||||
}*/
|
||||
|
||||
handle.reset();
|
||||
|
||||
if (interfaces) {
|
||||
pcap_freealldevs(interfaces);
|
||||
interfaces = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
/* 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);
|
||||
printf("%d. %s ", ++i, dev->name);
|
||||
if (dev->description) {
|
||||
printf("(%s)\n", dev->description);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
|
|
@ -111,13 +129,10 @@ void PcapCapture::print_interfaces() {
|
|||
* @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;
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
@ -135,49 +150,41 @@ void PcapCapture::callback(
|
|||
* Other Ethernet types are ignored.
|
||||
*/
|
||||
void PcapCapture::got_packet(const struct pcap_pkthdr *header, const u_char *packet) {
|
||||
if (!running) return;
|
||||
if (!running)
|
||||
return;
|
||||
|
||||
// --- Ethernet header ---
|
||||
const auto* ethernet = reinterpret_cast<const ether_header*>(packet);
|
||||
uint16_t ether_type = ntohs(ethernet->ether_type);
|
||||
|
||||
Packet packetView;
|
||||
// --- Ethernet header ---
|
||||
// const auto* ethernet = reinterpret_cast<const ether_header*>(packet + offset);
|
||||
uint16_t ether_type = get_ether_type(packet);
|
||||
|
||||
/* if we have a ipv4 type */
|
||||
if (ether_type == ETHERTYPE_IP) {
|
||||
IPv4 ip(packet + sizeof(struct ether_header));
|
||||
IPv4 ip(packet + offset);
|
||||
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->push(packetView);
|
||||
|
||||
}
|
||||
/* ipv6 type */
|
||||
if (ether_type == ETHERTYPE_IPV6) {
|
||||
IPv6 ip(packet + sizeof(struct ether_header));
|
||||
else if (ether_type == ETHERTYPE_IPV6) {
|
||||
IPv6 ip(packet + offset);
|
||||
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->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->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.
|
||||
|
|
@ -189,18 +196,17 @@ void PcapCapture::set_capabilities(std::string& interface, int num_packets, std:
|
|||
*
|
||||
* Used for post-capture analysis and exporting results.
|
||||
*/
|
||||
void PcapCapture::start_offline(std::string fpath) {
|
||||
handle = pcap_open_offline(fpath.c_str(), errbuf);
|
||||
void PcapCapture::start_offline(const std::string &fpath) {
|
||||
handle.reset(pcap_open_offline(fpath.c_str(), errbuf));
|
||||
if (handle == nullptr) {
|
||||
fprintf(stderr, "Error opening offline file: %s\n", errbuf);
|
||||
return;
|
||||
}
|
||||
datalink_type(pcap_datalink(handle.get()));
|
||||
|
||||
running = true;
|
||||
|
||||
pcap_loop(handle, num_packets,
|
||||
&PcapCapture::callback,
|
||||
reinterpret_cast<u_char*>(this));
|
||||
pcap_loop(handle.get(), num_packets, &PcapCapture::callback, reinterpret_cast<u_char *>(this));
|
||||
|
||||
running = false;
|
||||
}
|
||||
|
|
@ -1,64 +1,54 @@
|
|||
#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)")
|
||||
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)")
|
||||
("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")
|
||||
("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")
|
||||
("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")
|
||||
("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")
|
||||
("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")
|
||||
("limit,n", po::value<int>()->default_value(43), "Limit number of displayed entries")
|
||||
|
||||
("csv", po::value<std::string>(),
|
||||
"Export analysis results to CSV file")
|
||||
("csv", po::value<std::string>(), "Export analysis results to CSV file")
|
||||
|
||||
("json", po::value<std::string>(),
|
||||
"Export analysis results to JSON 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 << "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 << "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";
|
||||
}
|
||||
|
|
@ -1,60 +1,81 @@
|
|||
#include "../../include/cli/filter.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <stdexcept>
|
||||
|
||||
filter parse(std::string str) {
|
||||
filter parse(const std::string &str) {
|
||||
auto pos = str.find(':');
|
||||
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 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};
|
||||
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::string get_bpf_filter(const std::vector<filter> &f) {
|
||||
std::map<filter_type, std::vector<std::string>> groups;
|
||||
|
||||
for (const auto& x : f) {
|
||||
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 PROTOCOL:
|
||||
if (x.val == "dns")
|
||||
groups[PROTOCOL].emplace_back("port 53");
|
||||
else if (x.val == "http")
|
||||
groups[PROTOCOL].emplace_back("port 80");
|
||||
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:
|
||||
groups[IP_DEST].push_back("dst host " + 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 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;
|
||||
case PORT:
|
||||
groups[PORT].push_back("port " + x.val);
|
||||
break;
|
||||
case IP_TYPE: {
|
||||
if (x.val == "v4" || x.val == "4" || x.val == "ipv4")
|
||||
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:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::string result;
|
||||
bool first_group = true;
|
||||
|
||||
for (auto& [type, parts] : groups) {
|
||||
for (auto &[type, parts] : groups) {
|
||||
if (!first_group)
|
||||
result += " and ";
|
||||
first_group = false;
|
||||
|
|
@ -74,4 +95,3 @@ std::string get_bpf_filter(std::vector<filter>& f) {
|
|||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,211 +1,176 @@
|
|||
#include "../../include/packet/IP.hpp"
|
||||
|
||||
#include <cstdio>
|
||||
#include <arpa/inet.h>
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
#include <netinet/icmp6.h>
|
||||
#include <netinet/ip_icmp.h>
|
||||
#include <netinet/tcp.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() {
|
||||
return payload_len;
|
||||
}
|
||||
|
||||
TransportProtocol IP_class::get_protocol() const { return protocol; }
|
||||
std::string IP_class::get_source() { return src; }
|
||||
std::string IP_class::get_dest() { return dst; }
|
||||
|
||||
/*** Ipv4 ***/
|
||||
IPv4::IPv4(const u_char* data) {
|
||||
ip_hdr = reinterpret_cast<const ip*>(data);
|
||||
IPv4::IPv4(const u_char *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;
|
||||
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) {
|
||||
case IPPROTO_TCP:
|
||||
handle_tcp();
|
||||
return TransportProtocol::TCP;
|
||||
case IPPROTO_TCP:
|
||||
IPv4::handle_tcp();
|
||||
break;
|
||||
case IPPROTO_UDP:
|
||||
IPv4::handle_udp();
|
||||
break;
|
||||
|
||||
case IPPROTO_UDP:
|
||||
handle_udp();
|
||||
return TransportProtocol::UDP;
|
||||
case IPPROTO_ICMP:
|
||||
IPv4::handle_icmp();
|
||||
break;
|
||||
|
||||
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;
|
||||
case IPPROTO_ICMPV6:
|
||||
IPv4::handle_icmpv6();
|
||||
break;
|
||||
case IPPROTO_IGMP:
|
||||
IPv4::handle_igmp();
|
||||
break;
|
||||
default:
|
||||
protocol = TransportProtocol::UNKNOWN;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
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);
|
||||
|
||||
protocol = TransportProtocol::TCP;
|
||||
}
|
||||
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);
|
||||
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);
|
||||
}
|
||||
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; }
|
||||
|
||||
}
|
||||
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;
|
||||
}
|
||||
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() {
|
||||
IPv6::IPv6(const u_char *data) {
|
||||
ip_hdr = reinterpret_cast<const ip6_hdr *>(data);
|
||||
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) {
|
||||
switch (hdr) {
|
||||
case IPPROTO_TCP:
|
||||
handle_tcp();
|
||||
return TransportProtocol::TCP;
|
||||
case IPPROTO_TCP:
|
||||
IPv6::handle_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:
|
||||
handle_udp();
|
||||
return TransportProtocol::UDP;
|
||||
case IPPROTO_IGMP:
|
||||
IPv6::handle_igmp();
|
||||
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:
|
||||
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;
|
||||
const auto *frag = reinterpret_cast<const ip6_frag *>(ptr);
|
||||
hdr = frag->ip6f_nxt;
|
||||
ptr += sizeof(ip6_frag);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
protocol = TransportProtocol::UNKNOWN;
|
||||
return;
|
||||
}
|
||||
}
|
||||
ptr = nullptr;
|
||||
}
|
||||
|
||||
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);
|
||||
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;
|
||||
|
||||
protocol = TransportProtocol::TCP;
|
||||
ptr = nullptr;
|
||||
|
||||
}
|
||||
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);
|
||||
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);
|
||||
|
||||
protocol = TransportProtocol::UDP;
|
||||
ptr = nullptr;
|
||||
|
||||
}
|
||||
void IPv6::handle_icmp() {
|
||||
|
||||
protocol = TransportProtocol::ICMP;
|
||||
ptr = nullptr;
|
||||
}
|
||||
void IPv6::handle_icmpv6() {
|
||||
protocol = TransportProtocol::ICMP6;
|
||||
payload_len = ntohs(ip_hdr->ip6_plen) - sizeof(icmp6_hdr);
|
||||
ptr = nullptr;
|
||||
}
|
||||
void IPv6::handle_igmp() {
|
||||
|
||||
protocol = TransportProtocol::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;
|
||||
}
|
||||
|
||||
|
||||
uint16_t IPv6::get_src_port() { return src_port; }
|
||||
|
||||
uint16_t IPv6::get_dest_port() { return dest_port; }
|
||||
|
|
|
|||
|
|
@ -2,14 +2,12 @@
|
|||
#include <cstring>
|
||||
|
||||
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 (!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))
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
@ -25,25 +23,35 @@ 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;
|
||||
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;
|
||||
case 53:
|
||||
return ApplicationProtocol::DNS;
|
||||
case 443:
|
||||
return ApplicationProtocol::QUIC;
|
||||
case 123:
|
||||
return ApplicationProtocol::NTP;
|
||||
default:
|
||||
return ApplicationProtocol::UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
return ApplicationProtocol::UNKNOWN;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,8 @@
|
|||
#include "../../include/stats/protocolStats.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.
|
||||
*
|
||||
|
|
@ -19,17 +16,17 @@ Stats::Stats() {
|
|||
* Must be called only from capture thread.
|
||||
* Protected by mutex.
|
||||
*/
|
||||
void Stats::add_packet(Packet &packet) {
|
||||
void Stats::add_packet(const 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];
|
||||
auto &t = transport_map[packet.transport_protocol];
|
||||
t.packets++;
|
||||
t.bytes += packet.total_len;
|
||||
|
||||
auto& a = application_map[packet.application_protocol];
|
||||
auto &a = application_map[packet.application_protocol];
|
||||
a.packets++;
|
||||
a.bytes += packet.payload_len;
|
||||
|
||||
|
|
@ -42,32 +39,45 @@ void Stats::add_packet(Packet &packet) {
|
|||
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) {
|
||||
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";
|
||||
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) {
|
||||
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";
|
||||
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";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -83,29 +93,19 @@ const char* app_to_str(ApplicationProtocol p) {
|
|||
void Stats::update_transport_stats() {
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
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(
|
||||
transport_map.begin(), transport_map.end()
|
||||
);
|
||||
std::sort(tps.begin(), tps.end(),
|
||||
[](auto& a, auto& b) {
|
||||
return a.second.packets > b.second.packets;
|
||||
});
|
||||
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) {
|
||||
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)
|
||||
});
|
||||
|
||||
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.
|
||||
*
|
||||
|
|
@ -114,29 +114,19 @@ void Stats::update_transport_stats() {
|
|||
*/
|
||||
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::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;
|
||||
});
|
||||
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)", "%" });
|
||||
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;
|
||||
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)
|
||||
});
|
||||
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)});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -152,23 +142,16 @@ void Stats::update_ip_stats(size_t limit) {
|
|||
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::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;
|
||||
});
|
||||
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)}
|
||||
);
|
||||
|
||||
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)});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -183,17 +166,15 @@ void Stats::update_ip_stats(size_t limit) {
|
|||
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;
|
||||
});
|
||||
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", "%" });
|
||||
snapshot.pairs_rows.push_back({"Source", "Destination", "bytes received", "%"});
|
||||
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;
|
||||
snapshot.pairs_rows.push_back({
|
||||
pair.first,
|
||||
|
|
@ -202,16 +183,15 @@ void Stats::update_pairs(size_t limit) {
|
|||
std::format("{:.2f}", percent),
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void Stats::update_packets(){
|
||||
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"});
|
||||
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({
|
||||
packet.ip_version == IPVersion::v4 ? "IPv4" : "IPv6",
|
||||
transport_to_str(packet.transport_protocol),
|
||||
|
|
@ -264,13 +244,11 @@ void Stats::update_bandwidth() {
|
|||
last_b = snapshot.total_b;
|
||||
last_tick = now;
|
||||
const double alpha = 0.2;
|
||||
smooth_bandwidth =
|
||||
alpha * snapshot.bandwidth + (1.0 - alpha) * smooth_bandwidth;
|
||||
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);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -284,27 +262,23 @@ void Stats::update_bandwidth() {
|
|||
* - 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::ofstream file(filename);
|
||||
if (!file.is_open()) return;
|
||||
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";
|
||||
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) {
|
||||
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 << transport_to_str(proto) << "," << s.packets << "," << s.bytes << "," << percent << "\n";
|
||||
}
|
||||
file << "\n";
|
||||
|
||||
|
|
@ -312,10 +286,8 @@ void Stats::export_csv(const std::string& filename) {
|
|||
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";
|
||||
for (const auto &[proto, s] : application_map) {
|
||||
file << static_cast<int>(proto) << "," << s.packets << "," << s.bytes << "\n";
|
||||
}
|
||||
file << "\n";
|
||||
|
||||
|
|
@ -323,24 +295,19 @@ void Stats::export_csv(const std::string& filename) {
|
|||
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 << ","
|
||||
for (const auto &[ip, s] : ip_map) {
|
||||
file << ip << "," << s.packets_sent << "," << s.packets_received << "," << s.bytes_sent << ","
|
||||
<< s.bytes_received << "\n";
|
||||
}
|
||||
|
||||
|
||||
//bandwidth
|
||||
// bandwidth
|
||||
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.close();
|
||||
|
||||
}
|
||||
/**
|
||||
* @brief Exports statistics to JSON format.
|
||||
|
|
@ -350,10 +317,11 @@ void Stats::export_csv(const std::string& filename) {
|
|||
* - Visualization tools
|
||||
* - 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::ofstream file(filename);
|
||||
if (!file.is_open()) return;
|
||||
if (!file.is_open())
|
||||
return;
|
||||
|
||||
file << "{\n";
|
||||
|
||||
|
|
@ -367,8 +335,9 @@ void Stats::export_json(const std::string& filename) {
|
|||
// ===== Transport =====
|
||||
file << " \"transport\": [\n";
|
||||
bool first = true;
|
||||
for (const auto& [proto, s] : transport_map) {
|
||||
if (!first) file << ",\n";
|
||||
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;
|
||||
|
|
@ -385,8 +354,9 @@ void Stats::export_json(const std::string& filename) {
|
|||
// ===== IP stats =====
|
||||
file << " \"top_ips\": [\n";
|
||||
first = true;
|
||||
for (const auto& [ip, s] : ip_map) {
|
||||
if (!first) file << ",\n";
|
||||
for (const auto &[ip, s] : ip_map) {
|
||||
if (!first)
|
||||
file << ",\n";
|
||||
first = false;
|
||||
|
||||
file << " {\n";
|
||||
|
|
@ -402,8 +372,9 @@ void Stats::export_json(const std::string& filename) {
|
|||
file << ",\n \"communication_pairs\": [\n";
|
||||
first = true;
|
||||
|
||||
for (const auto& [pair, s] : pairs) {
|
||||
if (!first) file << ",\n";
|
||||
for (const auto &[pair, s] : pairs) {
|
||||
if (!first)
|
||||
file << ",\n";
|
||||
first = false;
|
||||
|
||||
file << " {\n";
|
||||
|
|
|
|||
Loading…
Reference in New Issue