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
|
sudo setcap cap_net_raw,cap_net_admin=eip ./network-traffic-analyzer
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Or you can use `just` command
|
||||||
|
```
|
||||||
|
just run
|
||||||
|
```
|
||||||
---
|
---
|
||||||
|
|
||||||
# Features
|
# Features
|
||||||
|
|
@ -59,29 +63,28 @@ sudo setcap cap_net_raw,cap_net_admin=eip ./network-traffic-analyzer
|
||||||
- FTXUI
|
- FTXUI
|
||||||
- CMake
|
- CMake
|
||||||
|
|
||||||
# Build
|
# Setup
|
||||||
```
|
## 1. clone the repo then
|
||||||
mkdir build && cd build
|
```bash
|
||||||
cmake ..
|
cd network-traffic-analyzer
|
||||||
make
|
./install.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
# Usage Example
|
# Usage Example
|
||||||
|
|
||||||
### Live capture on eth0
|
### Live capture on eth0
|
||||||
```
|
```
|
||||||
sudo ./network-traffic-analyzer -i eth0
|
just capture -i eth0
|
||||||
```
|
```
|
||||||
### Capture 100 packets
|
### Capture 100 packets
|
||||||
```
|
```
|
||||||
sudo ./network-traffic-analyzer -i wlan0 -c 100
|
just run -i wlan0 -c 100
|
||||||
```
|
```
|
||||||
### Analyze offline pcap file
|
### Analyze offline pcap file
|
||||||
```
|
```
|
||||||
sudo ./network-traffic-analyzer --offline traffic.pcap
|
just run --offline traffic.pcap
|
||||||
```
|
```
|
||||||
### Export results (json / csv)
|
### Export results (json / csv)
|
||||||
```
|
```
|
||||||
sudo ./network-traffic-analyzer --json result.json --csv result.csv
|
just run --json result.json --csv result.csv
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -4,37 +4,22 @@
|
||||||
#include <ftxui/dom/elements.hpp>
|
#include <ftxui/dom/elements.hpp>
|
||||||
|
|
||||||
class View {
|
class View {
|
||||||
public:
|
public:
|
||||||
ftxui::Element render(
|
ftxui::Element render(const StatsSnapshot &data, const std::string &interface, const std::string &filter,
|
||||||
const StatsSnapshot& data,
|
bool capture_finished, std::chrono::seconds timer);
|
||||||
const std::string& interface,
|
|
||||||
const std::string& filter,
|
|
||||||
bool capture_finished,
|
|
||||||
std::chrono::seconds timer
|
|
||||||
);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ftxui::Element render_header(
|
ftxui::Element render_header(const StatsSnapshot &data, const std::string &interface, const std::string &filter);
|
||||||
const StatsSnapshot& data,
|
ftxui::Element render_stats(const StatsSnapshot &data);
|
||||||
const std::string& interface,
|
|
||||||
const std::string& filter
|
|
||||||
);
|
|
||||||
ftxui::Element render_stats(const StatsSnapshot& data);
|
|
||||||
|
|
||||||
ftxui::Element render_transport(const StatsSnapshot& data);
|
ftxui::Element render_transport(const StatsSnapshot &data);
|
||||||
ftxui::Element render_application(const StatsSnapshot& data);
|
ftxui::Element render_application(const StatsSnapshot &data);
|
||||||
ftxui::Element render_ip(const StatsSnapshot& data);
|
ftxui::Element render_ip(const StatsSnapshot &data);
|
||||||
ftxui::Element render_pairs(const StatsSnapshot& data);
|
ftxui::Element render_pairs(const StatsSnapshot &data);
|
||||||
ftxui::Element render_bandwidth(const StatsSnapshot& data);
|
ftxui::Element render_bandwidth(const StatsSnapshot &data);
|
||||||
ftxui::Element render_packets(const StatsSnapshot& data);
|
ftxui::Element render_packets(const StatsSnapshot &data);
|
||||||
|
|
||||||
ftxui::Element render_footer(
|
ftxui::Element render_footer(bool capture_finished, std::chrono::seconds timer);
|
||||||
bool capture_finished,
|
|
||||||
std::chrono::seconds timer
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#endif // VIEW_HPP
|
||||||
|
|
||||||
|
|
||||||
#endif //VIEW_HPP
|
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,25 @@
|
||||||
#ifndef PCAPCAPTURE_HPP
|
#ifndef PCAPCAPTURE_HPP
|
||||||
#define PCAPCAPTURE_HPP
|
#define PCAPCAPTURE_HPP
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <queue>
|
|
||||||
#include <pcap/pcap.h>
|
|
||||||
#include <thread>
|
|
||||||
#include <deque>
|
#include <deque>
|
||||||
|
#include <memory>
|
||||||
|
#include <pcap/pcap.h>
|
||||||
|
#include <queue>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
//include C libraries
|
|
||||||
extern "C" {
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <sys/types.h>
|
|
||||||
#include <sys/socket.h>
|
|
||||||
#include <netinet/in.h>
|
|
||||||
#include <netinet/if_ether.h>
|
#include <netinet/if_ether.h>
|
||||||
#include <netinet/tcp.h>
|
|
||||||
#include <netinet/igmp.h>
|
#include <netinet/igmp.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <netinet/tcp.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/types.h>
|
||||||
#define SNAP_LEN 1518
|
#define SNAP_LEN 1518
|
||||||
}
|
|
||||||
|
|
||||||
#include "../packet/IP.hpp"
|
|
||||||
#include "../../include/stats/protocolStats.hpp"
|
#include "../../include/stats/protocolStats.hpp"
|
||||||
|
#include "../packet/IP.hpp"
|
||||||
#include "../packet/packet.hpp"
|
#include "../packet/packet.hpp"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -43,7 +40,7 @@ extern "C" {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class PcapCapture {
|
class PcapCapture {
|
||||||
private:
|
private:
|
||||||
/* libpcap error buffer */
|
/* libpcap error buffer */
|
||||||
char errbuf[PCAP_ERRBUF_SIZE];
|
char errbuf[PCAP_ERRBUF_SIZE];
|
||||||
|
|
||||||
|
|
@ -52,7 +49,10 @@ private:
|
||||||
/* compiled filter program (expression) */
|
/* compiled filter program (expression) */
|
||||||
struct bpf_program fp = {};
|
struct bpf_program fp = {};
|
||||||
/* Active pcap handle */
|
/* Active pcap handle */
|
||||||
pcap_t *handle = nullptr;
|
std::unique_ptr<pcap_t, decltype(&pcap_close)> handle{nullptr, &pcap_close};
|
||||||
|
void datalink_type(int type);
|
||||||
|
uint16_t offset = 0;
|
||||||
|
std::function<uint16_t(const u_char *)> get_ether_type;
|
||||||
|
|
||||||
/* Network mask and IP */
|
/* Network mask and IP */
|
||||||
bpf_u_int32 mask = 0;
|
bpf_u_int32 mask = 0;
|
||||||
|
|
@ -72,7 +72,7 @@ private:
|
||||||
* Since libpcap expects a C function pointer,
|
* Since libpcap expects a C function pointer,
|
||||||
* we use a static function and forward the call
|
* we use a static function and forward the call
|
||||||
* to the class instance.
|
* to the class instance.
|
||||||
*/
|
*/
|
||||||
static void callback(u_char *args, const struct pcap_pkthdr *header, const u_char *packet);
|
static void callback(u_char *args, const struct pcap_pkthdr *header, const u_char *packet);
|
||||||
// packet processing logic
|
// packet processing logic
|
||||||
void got_packet(const struct pcap_pkthdr *header, const u_char *packet);
|
void got_packet(const struct pcap_pkthdr *header, const u_char *packet);
|
||||||
|
|
@ -80,28 +80,23 @@ private:
|
||||||
/* Separate thread used for live capture */
|
/* Separate thread used for live capture */
|
||||||
std::thread thread;
|
std::thread thread;
|
||||||
std::atomic<bool> running{false};
|
std::atomic<bool> running{false};
|
||||||
|
void stop();
|
||||||
|
Stats *stats;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
~PcapCapture();
|
||||||
void print_interfaces();
|
void print_interfaces();
|
||||||
|
|
||||||
bool isRunning() {
|
bool isRunning() { return running; }
|
||||||
return running;
|
void setRunning(bool running) { this->running = running; }
|
||||||
}
|
|
||||||
void setRunning(bool running) {
|
|
||||||
this->running = running;
|
|
||||||
}
|
|
||||||
/* Pointer to statistics engine */
|
/* Pointer to statistics engine */
|
||||||
Stats* stats;
|
|
||||||
|
|
||||||
void set_capabilities(std::string& interface, int num_packets, std::string& filter_exp, int packets_limit, Stats* stats);
|
void set_capabilities(const std::string &interface, int num_packets, const std::string &filter_exp,
|
||||||
|
int packets_limit, Stats *stats);
|
||||||
void initialize();
|
void initialize();
|
||||||
|
|
||||||
|
|
||||||
void start();
|
void start();
|
||||||
void start_offline(std::string fpath);
|
void start_offline(const std::string &fpath);
|
||||||
void stop();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#endif // PCAPCAPTURE_HPP
|
||||||
#endif //PCAPCAPTURE_HPP
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ struct argsParser {
|
||||||
po::variables_map vm;
|
po::variables_map vm;
|
||||||
void print_help() const;
|
void print_help() const;
|
||||||
|
|
||||||
argsParser(int argc, char** argv);
|
argsParser(int argc, char **argv);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif //ARGSPARSE_HPP
|
#endif // ARGSPARSE_HPP
|
||||||
|
|
|
||||||
|
|
@ -3,24 +3,14 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
enum filter_type {
|
enum filter_type { PROTOCOL, PORT, IP_TYPE, IP_SRC, IP_DEST, NONE };
|
||||||
PROTOCOL,
|
|
||||||
PORT,
|
|
||||||
IP_TYPE,
|
|
||||||
IP_SRC,
|
|
||||||
IP_DEST,
|
|
||||||
NONE
|
|
||||||
};
|
|
||||||
|
|
||||||
struct filter {
|
struct filter {
|
||||||
filter_type type;
|
filter_type type;
|
||||||
std::string val;
|
std::string val;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
filter parse(const std::string &str);
|
||||||
|
std::string get_bpf_filter(const std::vector<filter> &f);
|
||||||
|
|
||||||
filter parse(std::string str);
|
#endif // FILTER_HPP
|
||||||
std::string get_bpf_filter(std::vector<filter>& f);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#endif //FILTER_HPP
|
|
||||||
|
|
|
||||||
|
|
@ -1,72 +1,75 @@
|
||||||
#ifndef IP_HPP
|
#ifndef IP_HPP
|
||||||
#define IP_HPP
|
#define IP_HPP
|
||||||
#include <string>
|
|
||||||
#include <netinet/ip.h>
|
|
||||||
#include <netinet/igmp.h>
|
|
||||||
#include <netinet/ip6.h>
|
|
||||||
#include "packet.hpp"
|
#include "packet.hpp"
|
||||||
|
#include <netinet/igmp.h>
|
||||||
|
#include <netinet/ip.h>
|
||||||
|
#include <netinet/ip6.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
/* virtual class for our IPv4, IPv6 classes */
|
/* virtual class for our IPv4, IPv6 classes */
|
||||||
class IP_class {
|
class IP_class {
|
||||||
protected:
|
protected:
|
||||||
virtual void handle_tcp() = 0;
|
virtual void handle_tcp() = 0;
|
||||||
virtual void handle_udp() = 0;
|
virtual void handle_udp() = 0;
|
||||||
virtual void handle_icmp() = 0;
|
virtual void handle_icmp() = 0;
|
||||||
virtual void handle_icmpv6() = 0;
|
virtual void handle_icmpv6() = 0;
|
||||||
virtual void handle_igmp() = 0;
|
virtual void handle_igmp() = 0;
|
||||||
|
|
||||||
uint16_t payload_len = 0;
|
uint16_t payload_len = 0;
|
||||||
public:
|
TransportProtocol protocol = TransportProtocol::UNKNOWN;
|
||||||
//getters
|
std::string src;
|
||||||
virtual std::string get_source() = 0;
|
std::string dst;
|
||||||
virtual std::string get_dest() = 0;
|
|
||||||
|
public:
|
||||||
|
std::string get_source();
|
||||||
|
std::string get_dest();
|
||||||
|
// getters
|
||||||
|
/*virtual std::string get_source() = 0;
|
||||||
|
virtual std::string get_dest() = 0;*/
|
||||||
virtual uint16_t get_src_port() = 0;
|
virtual uint16_t get_src_port() = 0;
|
||||||
virtual uint16_t get_dest_port() = 0;
|
virtual uint16_t get_dest_port() = 0;
|
||||||
virtual TransportProtocol get_protocol() = 0;
|
|
||||||
|
|
||||||
uint16_t get_payload_len();
|
TransportProtocol get_protocol() const;
|
||||||
|
uint16_t get_payload_len() const;
|
||||||
|
|
||||||
const uint8_t* payload_ptr = nullptr;
|
const uint8_t *payload_ptr = nullptr;
|
||||||
const uint8_t* get_payload_ptr() const { return payload_ptr; }
|
const uint8_t *get_payload_ptr() const { return payload_ptr; }
|
||||||
|
|
||||||
virtual ~IP_class() = default;
|
virtual ~IP_class() = default;
|
||||||
|
|
||||||
};
|
};
|
||||||
/*** ip4 ***/
|
/*** ip4 ***/
|
||||||
class IPv4 : public IP_class {
|
class IPv4 : public IP_class {
|
||||||
private:
|
private:
|
||||||
const ip* ip_hdr = nullptr;
|
const ip *ip_hdr = nullptr;
|
||||||
int ip_hdr_len = 0;
|
int ip_hdr_len = 0;
|
||||||
uint16_t src_port = 0;
|
uint16_t src_port = 0;
|
||||||
uint16_t dest_port = 0;
|
uint16_t dest_port = 0;
|
||||||
protected:
|
|
||||||
|
protected:
|
||||||
void handle_tcp() override;
|
void handle_tcp() override;
|
||||||
void handle_udp() override;
|
void handle_udp() override;
|
||||||
void handle_icmp() override;
|
void handle_icmp() override;
|
||||||
void handle_icmpv6() override;
|
void handle_icmpv6() override;
|
||||||
void handle_igmp() override;
|
void handle_igmp() override;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
TransportProtocol get_protocol() override;
|
|
||||||
std::string get_source() override;
|
|
||||||
std::string get_dest() override;
|
|
||||||
uint16_t get_src_port() override;
|
uint16_t get_src_port() override;
|
||||||
uint16_t get_dest_port() override;
|
uint16_t get_dest_port() override;
|
||||||
|
|
||||||
explicit IPv4(const u_char* data);
|
explicit IPv4(const u_char *data);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*** ipv6 ***/
|
/*** ipv6 ***/
|
||||||
class IPv6 : public IP_class{
|
class IPv6 : public IP_class {
|
||||||
protected:
|
protected:
|
||||||
void handle_tcp() override;
|
void handle_tcp() override;
|
||||||
void handle_udp() override;
|
void handle_udp() override;
|
||||||
void handle_icmp() override;
|
void handle_icmp() override;
|
||||||
void handle_icmpv6() override;
|
void handle_icmpv6() override;
|
||||||
void handle_igmp() override;
|
void handle_igmp() override;
|
||||||
private:
|
|
||||||
const ip6_hdr* ip_hdr = nullptr;
|
private:
|
||||||
|
const ip6_hdr *ip_hdr = nullptr;
|
||||||
int ip_hdr_len = 40;
|
int ip_hdr_len = 40;
|
||||||
|
|
||||||
in6_addr ip_source;
|
in6_addr ip_source;
|
||||||
|
|
@ -75,17 +78,13 @@ private:
|
||||||
uint16_t src_port;
|
uint16_t src_port;
|
||||||
uint16_t dest_port;
|
uint16_t dest_port;
|
||||||
|
|
||||||
const uint8_t* ptr = nullptr;
|
const uint8_t *ptr = nullptr;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit IPv6(const u_char* data);
|
explicit IPv6(const u_char *data);
|
||||||
|
|
||||||
TransportProtocol get_protocol() override;
|
|
||||||
std::string get_source() override;
|
|
||||||
std::string get_dest() override;
|
|
||||||
uint16_t get_src_port() override;
|
uint16_t get_src_port() override;
|
||||||
uint16_t get_dest_port() override;
|
uint16_t get_dest_port() override;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif //IP_HPP
|
#endif // IP_HPP
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
#ifndef PACKET_HPP
|
#ifndef PACKET_HPP
|
||||||
#define PACKET_HPP
|
#define PACKET_HPP
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <utility>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
enum IPVersion {
|
enum IPVersion {
|
||||||
v4, v6,
|
v4,
|
||||||
|
v6,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class TransportProtocol {
|
enum class TransportProtocol {
|
||||||
|
|
@ -32,9 +33,9 @@ struct Packet {
|
||||||
IPVersion ip_version;
|
IPVersion ip_version;
|
||||||
TransportProtocol transport_protocol;
|
TransportProtocol transport_protocol;
|
||||||
ApplicationProtocol application_protocol;
|
ApplicationProtocol application_protocol;
|
||||||
//src address
|
// src address
|
||||||
std::string src;
|
std::string src;
|
||||||
//dest address
|
// dest address
|
||||||
std::string dst;
|
std::string dst;
|
||||||
uint16_t src_port;
|
uint16_t src_port;
|
||||||
uint16_t dst_port;
|
uint16_t dst_port;
|
||||||
|
|
@ -42,18 +43,17 @@ struct Packet {
|
||||||
uint32_t total_len;
|
uint32_t total_len;
|
||||||
uint16_t payload_len;
|
uint16_t payload_len;
|
||||||
|
|
||||||
const uint8_t* payload_ptr;
|
const uint8_t *payload_ptr;
|
||||||
|
|
||||||
Packet(IPVersion version, TransportProtocol protocol, std::string src, std::string dst,
|
Packet(IPVersion version, TransportProtocol protocol, std::string src, std::string dst, uint16_t src_port,
|
||||||
uint16_t src_port, uint16_t dst_port, uint32_t total_len, uint16_t payload, const uint8_t* payload_ptr) : ip_version(version), transport_protocol(protocol),
|
uint16_t dst_port, uint32_t total_len, uint16_t payload, const uint8_t *payload_ptr)
|
||||||
src(std::move(src)), dst(std::move(dst)), src_port(src_port), dst_port(dst_port), total_len(total_len), payload_len(payload),
|
: ip_version(version), transport_protocol(protocol), src(std::move(src)), dst(std::move(dst)),
|
||||||
payload_ptr(payload_ptr)
|
src_port(src_port), dst_port(dst_port), total_len(total_len), payload_len(payload), payload_ptr(payload_ptr) {
|
||||||
{
|
application_protocol = get_application_protocol();
|
||||||
this->application_protocol = get_application_protocol();
|
this->payload_ptr = nullptr;
|
||||||
}
|
}
|
||||||
Packet() { }
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ApplicationProtocol get_application_protocol();
|
ApplicationProtocol get_application_protocol();
|
||||||
};
|
};
|
||||||
#endif //PACKET_HPP
|
#endif // PACKET_HPP
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,14 @@
|
||||||
#ifndef PROTOCOLSTATS_HPP
|
#ifndef PROTOCOLSTATS_HPP
|
||||||
#define PROTOCOLSTATS_HPP
|
#define PROTOCOLSTATS_HPP
|
||||||
|
|
||||||
#include <chrono>
|
|
||||||
#include <cstdint>
|
|
||||||
#include "../packet/packet.hpp"
|
#include "../packet/packet.hpp"
|
||||||
#include <unordered_map>
|
#include "ftxui/dom/elements.hpp"
|
||||||
|
#include <chrono>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <fstream>
|
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
#include "ftxui/dom/elements.hpp"
|
#include <unordered_map>
|
||||||
|
|
||||||
using namespace ftxui;
|
|
||||||
struct protocolStats {
|
struct protocolStats {
|
||||||
uint32_t packets = 0;
|
uint32_t packets = 0;
|
||||||
uint32_t bytes = 0;
|
uint32_t bytes = 0;
|
||||||
|
|
@ -43,14 +40,12 @@ struct StatsSnapshot {
|
||||||
std::vector<std::vector<std::string>> packets_rows;
|
std::vector<std::vector<std::string>> packets_rows;
|
||||||
|
|
||||||
uint32_t total_p = 0, total_b = 0;
|
uint32_t total_p = 0, total_b = 0;
|
||||||
//bandwidth
|
// bandwidth
|
||||||
std::vector<BandwidthPoint> bandwidth_history;
|
std::vector<BandwidthPoint> bandwidth_history;
|
||||||
double bandwidth = 0;
|
double bandwidth = 0;
|
||||||
mutable double max_bandwidth = 0;
|
double max_bandwidth = 0;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Thread-safe statistics engine.
|
* @brief Thread-safe statistics engine.
|
||||||
*
|
*
|
||||||
|
|
@ -64,14 +59,13 @@ struct StatsSnapshot {
|
||||||
* All write operations are protected by mutex.
|
* All write operations are protected by mutex.
|
||||||
*/
|
*/
|
||||||
class Stats {
|
class Stats {
|
||||||
private:
|
private:
|
||||||
std::mutex mtx;
|
std::mutex mtx;
|
||||||
|
|
||||||
uint32_t last_b = 0;
|
uint32_t last_b = 0;
|
||||||
|
|
||||||
std::chrono::steady_clock::time_point last_tick;
|
std::chrono::steady_clock::time_point last_tick;
|
||||||
|
|
||||||
|
|
||||||
std::unordered_map<TransportProtocol, protocolStats> transport_map;
|
std::unordered_map<TransportProtocol, protocolStats> transport_map;
|
||||||
std::unordered_map<ApplicationProtocol, protocolStats> application_map;
|
std::unordered_map<ApplicationProtocol, protocolStats> application_map;
|
||||||
std::unordered_map<std::string, IPStats> ip_map;
|
std::unordered_map<std::string, IPStats> ip_map;
|
||||||
|
|
@ -81,10 +75,10 @@ private:
|
||||||
std::deque<Packet> packets;
|
std::deque<Packet> packets;
|
||||||
int limit_packets = 10;
|
int limit_packets = 10;
|
||||||
|
|
||||||
|
StatsSnapshot snapshot;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
void push(const Packet &p) {
|
||||||
void push(const Packet& p) {
|
|
||||||
std::lock_guard<std::mutex> lock(mtx);
|
std::lock_guard<std::mutex> lock(mtx);
|
||||||
if (packets.size() > static_cast<long unsigned int>(limit_packets)) {
|
if (packets.size() > static_cast<long unsigned int>(limit_packets)) {
|
||||||
packets.pop_front();
|
packets.pop_front();
|
||||||
|
|
@ -92,16 +86,17 @@ public:
|
||||||
packets.push_back(p);
|
packets.push_back(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
StatsSnapshot snapshot;
|
StatsSnapshot get_snapshot() {
|
||||||
|
std::lock_guard<std::mutex> lock(mtx);
|
||||||
|
return snapshot;
|
||||||
|
}
|
||||||
void update_bandwidth();
|
void update_bandwidth();
|
||||||
double smooth_value(size_t i, size_t start);
|
double smooth_value(size_t i, size_t start);
|
||||||
double smooth_bandwidth = 0.0;
|
double smooth_bandwidth = 0.0;
|
||||||
|
|
||||||
void set_packets_limit(int limit) {
|
void set_packets_limit(int limit) { limit_packets = limit; }
|
||||||
limit_packets = limit;
|
|
||||||
}
|
|
||||||
|
|
||||||
void add_packet(Packet &packet);
|
void add_packet(const Packet &packet);
|
||||||
|
|
||||||
void update_transport_stats();
|
void update_transport_stats();
|
||||||
void update_application_stats();
|
void update_application_stats();
|
||||||
|
|
@ -109,12 +104,10 @@ public:
|
||||||
void update_pairs(size_t limit = 10);
|
void update_pairs(size_t limit = 10);
|
||||||
void update_packets();
|
void update_packets();
|
||||||
|
|
||||||
void export_csv(const std::string& filename);
|
void export_csv(const std::string &filename);
|
||||||
void export_json(const std::string& filename);
|
void export_json(const std::string &filename);
|
||||||
|
|
||||||
Stats();
|
Stats();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#endif // PROTOCOLSTATS_HPP
|
||||||
|
|
||||||
#endif //PROTOCOLSTATS_HPP
|
|
||||||
|
|
|
||||||
|
|
@ -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 "include/capture/pcapCapture.hpp"
|
||||||
#include <iostream>
|
|
||||||
#include <pcap/pcap.h>
|
|
||||||
#include <boost/program_options.hpp>
|
|
||||||
#include "include/cli/filter.hpp"
|
#include "include/cli/filter.hpp"
|
||||||
#include <ftxui/component/screen_interactive.hpp>
|
#include <boost/program_options.hpp>
|
||||||
#include <ftxui/component/component.hpp>
|
#include <ftxui/component/component.hpp>
|
||||||
#include <ftxui/component/component_options.hpp>
|
#include <ftxui/component/component_options.hpp>
|
||||||
|
#include <ftxui/component/screen_interactive.hpp>
|
||||||
|
#include <iostream>
|
||||||
|
#include <pcap/pcap.h>
|
||||||
|
|
||||||
|
|
||||||
#include "include/cli/argsParse.hpp"
|
|
||||||
#include "include/TUI/view.hpp"
|
#include "include/TUI/view.hpp"
|
||||||
#define SNAP_LEN 1518
|
#include "include/cli/argsParse.hpp"
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv) {
|
||||||
{
|
/* initialize stats */
|
||||||
|
Stats stats;
|
||||||
/* initialize capture */
|
/* initialize capture */
|
||||||
PcapCapture capture;
|
PcapCapture capture;
|
||||||
capture.initialize();
|
capture.initialize();
|
||||||
|
|
||||||
/* initialize stats */
|
|
||||||
Stats stats;
|
|
||||||
|
|
||||||
/* initializing the command line parser */
|
/* initializing the command line parser */
|
||||||
argsParser parser(argc, argv);
|
argsParser parser(argc, argv);
|
||||||
|
|
||||||
|
|
@ -43,8 +39,8 @@ int main(int argc, char **argv)
|
||||||
/* get a filter, use vector for multiple */
|
/* get a filter, use vector for multiple */
|
||||||
std::vector<filter> filters;
|
std::vector<filter> filters;
|
||||||
if (parser.vm.contains("filter")) {
|
if (parser.vm.contains("filter")) {
|
||||||
auto& f = parser.vm["filter"].as<std::vector<std::string>>();
|
auto &f = parser.vm["filter"].as<std::vector<std::string>>();
|
||||||
for (auto& x : f) {
|
for (auto &x : f) {
|
||||||
filters.push_back(parse(x));
|
filters.push_back(parse(x));
|
||||||
filterString += x + " ";
|
filterString += x + " ";
|
||||||
}
|
}
|
||||||
|
|
@ -57,97 +53,102 @@ int main(int argc, char **argv)
|
||||||
/* set the flags to capture engine */
|
/* set the flags to capture engine */
|
||||||
capture.set_capabilities(interface, count, expression, limit, &stats);
|
capture.set_capabilities(interface, count, expression, limit, &stats);
|
||||||
|
|
||||||
|
|
||||||
std::atomic<bool> capture_finished = false;
|
std::atomic<bool> capture_finished = false;
|
||||||
|
std::atomic<bool> ui_running = true;
|
||||||
/* if we capture packets offline, we read the file in full, then print the result */
|
/* if we capture packets offline, we read the file in full, then print the result */
|
||||||
if (isOffline) {
|
if (isOffline) {
|
||||||
|
|
||||||
capture.start_offline(parser.vm["offline"].as<std::string>());
|
capture.start_offline(parser.vm["offline"].as<std::string>());
|
||||||
|
|
||||||
/* full recalculation of statistics after file processing */
|
/* full recalculation of statistics after file processing */
|
||||||
stats.update_packets();
|
stats.update_packets();
|
||||||
stats.update_application_stats();
|
stats.update_application_stats();
|
||||||
stats.update_transport_stats();
|
stats.update_transport_stats();
|
||||||
stats.update_ip_stats(10);
|
stats.update_ip_stats(10);
|
||||||
stats.update_pairs();
|
stats.update_pairs();
|
||||||
stats.update_bandwidth();
|
stats.update_bandwidth();
|
||||||
}
|
}
|
||||||
/* otherwise start live capture */
|
/* otherwise start live capture */
|
||||||
else {
|
else {
|
||||||
capture.start();
|
capture.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* UI */
|
/* UI */
|
||||||
using namespace ftxui;
|
// Timer
|
||||||
auto screen = ScreenInteractive::Fullscreen();
|
|
||||||
/* our class for UI */
|
|
||||||
View view;
|
|
||||||
|
|
||||||
/* timer */
|
|
||||||
std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now();
|
std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now();
|
||||||
std::chrono::seconds timer{0};
|
std::atomic<std::chrono::seconds> timer;
|
||||||
|
auto screen = ftxui::ScreenInteractive::Fullscreen();
|
||||||
|
|
||||||
auto renderer = Renderer([&] {
|
/* our class for UI */
|
||||||
auto snapshot = stats.snapshot;
|
View view;
|
||||||
return view.render(snapshot,
|
std::mutex render_mtx;
|
||||||
interface,
|
std::mutex event_mtx;
|
||||||
filterString,
|
ftxui::Element current_render = isOffline
|
||||||
capture_finished,
|
? view.render(stats.get_snapshot(), interface, filterString, true, timer.load())
|
||||||
timer);
|
: ftxui::text("Starting capture...");
|
||||||
});
|
|
||||||
|
|
||||||
auto component = CatchEvent(renderer, [&](Event e) {
|
std::mutex screen_mtx;
|
||||||
if (e == Event::Character('q') || e == Event::Escape) {
|
|
||||||
capture.stop();
|
|
||||||
screen.Exit();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
|
auto component = ftxui::Renderer([&] {
|
||||||
|
std::lock_guard<std::mutex> lock(render_mtx);
|
||||||
|
return current_render;
|
||||||
|
});
|
||||||
|
|
||||||
std::thread updater;
|
component |= ftxui::CatchEvent([&](ftxui::Event e) {
|
||||||
|
if (e == ftxui::Event::Character('q') || e == ftxui::Event::Escape) {
|
||||||
|
ui_running = false;
|
||||||
|
screen.Exit();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
std::thread application_thread;
|
||||||
|
if (!isOffline) {
|
||||||
|
application_thread = std::thread([&] {
|
||||||
|
while (!capture_finished && ui_running) {
|
||||||
|
|
||||||
if (!isOffline) {
|
auto now = std::chrono::steady_clock::now();
|
||||||
updater = std::thread([&] {
|
timer.store(std::chrono::duration_cast<std::chrono::seconds>(now - begin));
|
||||||
while (capture.isRunning()) {
|
|
||||||
|
|
||||||
stats.update_packets();
|
if (timer.load() >= std::chrono::seconds(time) || !capture.isRunning()) {
|
||||||
stats.update_application_stats();
|
capture_finished = true;
|
||||||
stats.update_transport_stats();
|
}
|
||||||
stats.update_ip_stats(10);
|
|
||||||
stats.update_pairs();
|
|
||||||
stats.update_bandwidth();
|
|
||||||
|
|
||||||
screen.PostEvent(Event::Custom);
|
stats.update_packets();
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(300));
|
stats.update_application_stats();
|
||||||
|
stats.update_transport_stats();
|
||||||
|
stats.update_ip_stats(10);
|
||||||
|
stats.update_pairs();
|
||||||
|
stats.update_bandwidth();
|
||||||
|
|
||||||
auto current_time = std::chrono::steady_clock::now();
|
ftxui::Element new_frame =
|
||||||
timer = std::chrono::duration_cast<std::chrono::seconds>(
|
view.render(stats.get_snapshot(), interface, filterString, capture_finished, timer.load());
|
||||||
current_time - begin);
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(render_mtx);
|
||||||
|
current_render = new_frame;
|
||||||
|
}
|
||||||
|
if (ui_running) {
|
||||||
|
screen.PostEvent(ftxui::Event::Custom);
|
||||||
|
}
|
||||||
|
|
||||||
if (timer >= std::chrono::seconds(time))
|
// std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
||||||
break;
|
}
|
||||||
}
|
});
|
||||||
|
}
|
||||||
|
|
||||||
capture_finished = true;
|
screen.Loop(component);
|
||||||
screen.PostEvent(Event::Custom);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
screen.Loop(component);
|
ui_running = false;
|
||||||
|
capture_finished = true;
|
||||||
|
|
||||||
if (updater.joinable())
|
if (application_thread.joinable())
|
||||||
updater.join();
|
application_thread.join();
|
||||||
|
|
||||||
/* check export flags */
|
// Export stats if needed
|
||||||
if (parser.vm.contains("csv")) {
|
if (parser.vm.contains("csv"))
|
||||||
stats.export_csv(parser.vm["csv"].as<std::string>());
|
stats.export_csv(parser.vm["csv"].as<std::string>());
|
||||||
}
|
if (parser.vm.contains("json"))
|
||||||
if (parser.vm.contains("json")) {
|
stats.export_json(parser.vm["json"].as<std::string>());
|
||||||
stats.export_json(parser.vm["json"].as<std::string>());
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,53 +1,39 @@
|
||||||
#include "../../include/TUI/view.hpp"
|
#include "../../include/TUI/view.hpp"
|
||||||
#include "ftxui/dom/table.hpp"
|
#include "ftxui/dom/table.hpp"
|
||||||
|
|
||||||
|
using namespace ftxui;
|
||||||
ftxui::Element View::render(const StatsSnapshot& data, const std::string& interface, const std::string& filter,
|
ftxui::Element View::render(const StatsSnapshot &data, const std::string &interface, const std::string &filter,
|
||||||
bool capture_finished, std::chrono::seconds timer) {
|
bool capture_finished, std::chrono::seconds timer) {
|
||||||
auto header = render_header(data, interface, filter);
|
auto header = render_header(data, interface, filter);
|
||||||
|
|
||||||
auto transport_section =
|
auto transport_section = hbox({
|
||||||
hbox({
|
render_transport(data) | flex,
|
||||||
render_transport(data) | flex,
|
separator(),
|
||||||
separator(),
|
render_application(data) | flex,
|
||||||
render_application(data) | flex,
|
separator(),
|
||||||
separator(),
|
render_pairs(data) | flex,
|
||||||
render_pairs(data) | flex,
|
}) |
|
||||||
}) | border;
|
border;
|
||||||
|
|
||||||
auto ip_section =
|
auto ip_section = hbox({render_ip(data) | border | size(HEIGHT, LESS_THAN, 10) | frame | vscroll_indicator,
|
||||||
hbox({
|
|
||||||
render_ip(data)
|
|
||||||
| border
|
|
||||||
| size(HEIGHT, LESS_THAN, 10)
|
|
||||||
| frame
|
|
||||||
| vscroll_indicator,
|
|
||||||
|
|
||||||
render_bandwidth(data)
|
render_bandwidth(data) | border | flex});
|
||||||
| border
|
|
||||||
| flex
|
|
||||||
});
|
|
||||||
|
|
||||||
auto left_panel =
|
auto left_panel = vbox({
|
||||||
vbox({
|
transport_section,
|
||||||
transport_section,
|
separator(),
|
||||||
separator(),
|
ip_section,
|
||||||
ip_section,
|
}) |
|
||||||
}) | flex_grow;
|
flex_grow;
|
||||||
|
|
||||||
auto right_panel =
|
auto right_panel = render_packets(data) | border | size(WIDTH, EQUAL, 100) | frame | vscroll_indicator;
|
||||||
render_packets(data)
|
|
||||||
| border
|
|
||||||
| size(WIDTH, EQUAL, 100)
|
|
||||||
| frame
|
|
||||||
| vscroll_indicator;
|
|
||||||
|
|
||||||
auto body =
|
auto body = hbox({
|
||||||
hbox({
|
left_panel,
|
||||||
left_panel,
|
separator(),
|
||||||
separator(),
|
right_panel,
|
||||||
right_panel,
|
}) |
|
||||||
}) | flex;
|
flex;
|
||||||
|
|
||||||
auto footer = render_footer(capture_finished, timer);
|
auto footer = render_footer(capture_finished, timer);
|
||||||
|
|
||||||
|
|
@ -68,41 +54,33 @@ ftxui::Element View::render(const StatsSnapshot& data, const std::string& interf
|
||||||
* - Active filter
|
* - Active filter
|
||||||
* - Traffic summary
|
* - Traffic summary
|
||||||
*/
|
*/
|
||||||
ftxui::Element View::render_header(const StatsSnapshot& data, const std::string& interface, const std::string& filter) {
|
ftxui::Element View::render_header(const StatsSnapshot &data, const std::string &interface, const std::string &filter) {
|
||||||
return hbox({
|
return hbox({
|
||||||
vbox({
|
vbox({
|
||||||
text("Network Traffic Analyzer") | bold,
|
text("Network Traffic Analyzer") | bold,
|
||||||
text("Interface: " + interface),
|
text("Interface: " + interface),
|
||||||
text("Filter: " + filter),
|
text("Filter: " + filter),
|
||||||
}) | flex,
|
}) | flex,
|
||||||
separator(),
|
separator(),
|
||||||
render_stats(data) | flex,
|
render_stats(data) | flex,
|
||||||
}) | border;
|
}) |
|
||||||
|
border;
|
||||||
}
|
}
|
||||||
|
|
||||||
ftxui::Element View::render_stats(const StatsSnapshot& data) {
|
ftxui::Element View::render_stats(const StatsSnapshot &data) {
|
||||||
return vbox({
|
return vbox({text("=== Traffic summary ===") | bold, text("Total packets: " + std::to_string(data.total_p)),
|
||||||
text("=== Traffic summary ===") | bold,
|
text(std::format("Total bytes : {:.2f} MB", data.total_b / (1024.0 * 1024.0)))}) |
|
||||||
text("Total packets: " + std::to_string(data.total_p)),
|
flex;
|
||||||
text(std::format(
|
|
||||||
"Total bytes : {:.2f} MB",
|
|
||||||
data.total_b / (1024.0 * 1024.0)
|
|
||||||
))
|
|
||||||
}) | flex;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
ftxui::Element View::render_footer(bool capture_finished, std::chrono::seconds timer) {
|
ftxui::Element View::render_footer(bool capture_finished, std::chrono::seconds timer) {
|
||||||
return Element({
|
return Element({capture_finished
|
||||||
capture_finished
|
? text("Capture finished (" + std::format("{}", timer) + "). Press 'q' or Esc to exit.") |
|
||||||
? text("Capture finished (" + std::format("{}", timer) + "). Press 'q' or Esc to exit.")
|
bold | color(Color::Yellow) | center
|
||||||
| bold | color(Color::Yellow) | center
|
: text("time: " + std::format("{}", timer) + ". Press 'q' or Esc to exit.") | center |
|
||||||
: text("time: " + std::format("{}", timer) + ". Press 'q' or Esc to exit.") | center
|
size(HEIGHT, EQUAL, 1)});
|
||||||
| size(HEIGHT, EQUAL, 1)
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
}
|
||||||
ftxui::Element View::render_transport(const StatsSnapshot& data) {
|
ftxui::Element View::render_transport(const StatsSnapshot &data) {
|
||||||
Table table(data.transport_rows);
|
Table table(data.transport_rows);
|
||||||
table.SelectAll().Border(LIGHT);
|
table.SelectAll().Border(LIGHT);
|
||||||
|
|
||||||
|
|
@ -111,12 +89,9 @@ ftxui::Element View::render_transport(const StatsSnapshot& data) {
|
||||||
table.SelectRow(0).Decorate(bold);
|
table.SelectRow(0).Decorate(bold);
|
||||||
table.SelectRow(0).Border(DOUBLE);
|
table.SelectRow(0).Border(DOUBLE);
|
||||||
|
|
||||||
return vbox({
|
return vbox({text("=== Transport protocols === ") | bold, table.Render()}) | flex;
|
||||||
text("=== Transport protocols === ") | bold,
|
|
||||||
table.Render()
|
|
||||||
}) | flex;
|
|
||||||
}
|
}
|
||||||
ftxui::Element View::render_application(const StatsSnapshot& data) {
|
ftxui::Element View::render_application(const StatsSnapshot &data) {
|
||||||
Table table(data.app_rows);
|
Table table(data.app_rows);
|
||||||
table.SelectAll().Border(LIGHT);
|
table.SelectAll().Border(LIGHT);
|
||||||
|
|
||||||
|
|
@ -125,12 +100,9 @@ ftxui::Element View::render_application(const StatsSnapshot& data) {
|
||||||
table.SelectRow(0).Decorate(bold);
|
table.SelectRow(0).Decorate(bold);
|
||||||
table.SelectRow(0).Border(DOUBLE);
|
table.SelectRow(0).Border(DOUBLE);
|
||||||
|
|
||||||
return vbox({
|
return vbox({text("=== Application protocols ===") | bold, table.Render()}) | flex;
|
||||||
text("=== Application protocols ===") | bold,
|
|
||||||
table.Render()
|
|
||||||
}) | flex;
|
|
||||||
}
|
}
|
||||||
ftxui::Element View::render_ip(const StatsSnapshot& data) {
|
ftxui::Element View::render_ip(const StatsSnapshot &data) {
|
||||||
|
|
||||||
Table table(data.rows);
|
Table table(data.rows);
|
||||||
table.SelectAll().Border(LIGHT);
|
table.SelectAll().Border(LIGHT);
|
||||||
|
|
@ -140,13 +112,12 @@ ftxui::Element View::render_ip(const StatsSnapshot& data) {
|
||||||
table.SelectRow(0).Decorate(bold);
|
table.SelectRow(0).Decorate(bold);
|
||||||
table.SelectRow(0).Border(DOUBLE);
|
table.SelectRow(0).Border(DOUBLE);
|
||||||
|
|
||||||
return vbox({
|
return vbox({text("=== Top IP addresses ===") | bold,
|
||||||
text("=== Top IP addresses ===") | bold,
|
|
||||||
|
|
||||||
table.Render()
|
table.Render()}) |
|
||||||
}) | flex;
|
flex;
|
||||||
}
|
}
|
||||||
ftxui::Element View::render_pairs(const StatsSnapshot& data) {
|
ftxui::Element View::render_pairs(const StatsSnapshot &data) {
|
||||||
Table table(data.pairs_rows);
|
Table table(data.pairs_rows);
|
||||||
table.SelectAll().Border(LIGHT);
|
table.SelectAll().Border(LIGHT);
|
||||||
|
|
||||||
|
|
@ -155,10 +126,7 @@ ftxui::Element View::render_pairs(const StatsSnapshot& data) {
|
||||||
table.SelectRow(0).Decorate(bold);
|
table.SelectRow(0).Decorate(bold);
|
||||||
table.SelectRow(0).Border(DOUBLE);
|
table.SelectRow(0).Border(DOUBLE);
|
||||||
|
|
||||||
return vbox({
|
return vbox({text("=== Top communication pairs ===") | bold, table.Render()}) | flex;
|
||||||
text("=== Top communication pairs ===") | bold,
|
|
||||||
table.Render()
|
|
||||||
}) | flex;
|
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* @brief Renders bandwidth graph.
|
* @brief Renders bandwidth graph.
|
||||||
|
|
@ -167,8 +135,7 @@ ftxui::Element View::render_pairs(const StatsSnapshot& data) {
|
||||||
* Scales dynamically based on max bandwidth.
|
* Scales dynamically based on max bandwidth.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
ftxui::Element View::render_bandwidth(const StatsSnapshot& data) {
|
ftxui::Element View::render_bandwidth(const StatsSnapshot &data) {
|
||||||
using namespace ftxui;
|
|
||||||
|
|
||||||
GraphFunction fn = [this, data](int width, int height) {
|
GraphFunction fn = [this, data](int width, int height) {
|
||||||
std::vector<int> output(width, 0);
|
std::vector<int> output(width, 0);
|
||||||
|
|
@ -179,7 +146,6 @@ ftxui::Element View::render_bandwidth(const StatsSnapshot& data) {
|
||||||
size_t n = data.bandwidth_history.size();
|
size_t n = data.bandwidth_history.size();
|
||||||
size_t start = n > 50 ? n - 50 : 0;
|
size_t start = n > 50 ? n - 50 : 0;
|
||||||
|
|
||||||
|
|
||||||
double max_bw = 1.0;
|
double max_bw = 1.0;
|
||||||
for (size_t i = start; i < n; ++i)
|
for (size_t i = start; i < n; ++i)
|
||||||
max_bw = std::max(max_bw, data.bandwidth_history[i].bytes_per_sec);
|
max_bw = std::max(max_bw, data.bandwidth_history[i].bytes_per_sec);
|
||||||
|
|
@ -188,16 +154,13 @@ ftxui::Element View::render_bandwidth(const StatsSnapshot& data) {
|
||||||
|
|
||||||
double t = (double)x / (width - 1);
|
double t = (double)x / (width - 1);
|
||||||
|
|
||||||
|
|
||||||
double idx_f = start + t * (n - start - 1);
|
double idx_f = start + t * (n - start - 1);
|
||||||
size_t i0 = (size_t)idx_f;
|
size_t i0 = (size_t)idx_f;
|
||||||
size_t i1 = std::min(i0 + 1, n - 1);
|
size_t i1 = std::min(i0 + 1, n - 1);
|
||||||
|
|
||||||
|
|
||||||
double frac = idx_f - i0;
|
double frac = idx_f - i0;
|
||||||
double bw =
|
double bw = data.bandwidth_history[i0].bytes_per_sec * (1.0 - frac) +
|
||||||
data.bandwidth_history[i0].bytes_per_sec * (1.0 - frac) +
|
data.bandwidth_history[i1].bytes_per_sec * frac;
|
||||||
data.bandwidth_history[i1].bytes_per_sec * frac;
|
|
||||||
|
|
||||||
double v = bw / max_bw;
|
double v = bw / max_bw;
|
||||||
output[x] = static_cast<int>(v * (height - 1));
|
output[x] = static_cast<int>(v * (height - 1));
|
||||||
|
|
@ -207,13 +170,10 @@ ftxui::Element View::render_bandwidth(const StatsSnapshot& data) {
|
||||||
};
|
};
|
||||||
return vbox({
|
return vbox({
|
||||||
text(std::format(" Bandwidth: {:.2f} KB / max: {:.2f} KB", data.bandwidth, data.max_bandwidth)) | bold,
|
text(std::format(" Bandwidth: {:.2f} KB / max: {:.2f} KB", data.bandwidth, data.max_bandwidth)) | bold,
|
||||||
graph(fn)
|
graph(fn) | size(HEIGHT, EQUAL, 20) | size(WIDTH, EQUAL, 60) | border | color(Color::Green),
|
||||||
| size(HEIGHT, EQUAL, 20) | size(WIDTH, EQUAL, 60)
|
});
|
||||||
| border
|
|
||||||
| color(Color::Green),
|
|
||||||
}) ;
|
|
||||||
}
|
}
|
||||||
ftxui::Element View::render_packets(const StatsSnapshot& data) {
|
ftxui::Element View::render_packets(const StatsSnapshot &data) {
|
||||||
Table table(data.packets_rows);
|
Table table(data.packets_rows);
|
||||||
table.SelectAll().Border(LIGHT);
|
table.SelectAll().Border(LIGHT);
|
||||||
|
|
||||||
|
|
@ -221,9 +181,8 @@ ftxui::Element View::render_packets(const StatsSnapshot& data) {
|
||||||
table.SelectRow(0).SeparatorVertical(LIGHT);
|
table.SelectRow(0).SeparatorVertical(LIGHT);
|
||||||
table.SelectRow(0).Decorate(bold);
|
table.SelectRow(0).Decorate(bold);
|
||||||
table.SelectRow(0).Border(DOUBLE);
|
table.SelectRow(0).Border(DOUBLE);
|
||||||
return vbox({
|
return vbox({text("=== Packets ===") | bold,
|
||||||
text("=== Packets ===") | bold,
|
|
||||||
|
|
||||||
table.Render() | flex
|
table.Render() | flex}) |
|
||||||
}) | flex;
|
flex;
|
||||||
}
|
}
|
||||||
|
|
@ -1,102 +1,120 @@
|
||||||
#include "../../include/capture/pcapCapture.hpp"
|
#include "../../include/capture/pcapCapture.hpp"
|
||||||
#include "../../include/stats/protocolStats.hpp"
|
#include "../../include/stats/protocolStats.hpp"
|
||||||
|
|
||||||
|
|
||||||
/* get a list of all available network interfaces */
|
/* get a list of all available network interfaces */
|
||||||
void PcapCapture::initialize() {
|
void PcapCapture::initialize() {
|
||||||
/* find all devs available in network, save them to pcap_if_t struct (interfaces) */
|
/* find all devs available in network, save them to pcap_if_t struct (interfaces) */
|
||||||
if (pcap_findalldevs(&interfaces, errbuf) == -1) {
|
if (pcap_findalldevs(&interfaces, errbuf) == -1) {
|
||||||
fprintf(stderr, "Error: pcap_findalldevs has been failed - %s\n", errbuf);
|
throw std::runtime_error("Error: pcap_findalldevs has been failed");
|
||||||
|
/*fprintf(stderr, "Error: pcap_findalldevs has been failed - %s\n", errbuf);*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PcapCapture::datalink_type(int type) {
|
||||||
|
switch (type) {
|
||||||
|
|
||||||
|
case DLT_EN10MB: {
|
||||||
|
offset = 14;
|
||||||
|
get_ether_type = [](const u_char *p) {
|
||||||
|
auto *eth = reinterpret_cast<const ether_header *>(p);
|
||||||
|
return ntohs(eth->ether_type);
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case DLT_LINUX_SLL: {
|
||||||
|
offset = 16;
|
||||||
|
get_ether_type = [](const u_char *p) { return ntohs(*reinterpret_cast<const uint16_t *>(p + 14)); };
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case DLT_LINUX_SLL2: {
|
||||||
|
offset = 20;
|
||||||
|
get_ether_type = [](const u_char *p) { return ntohs(*reinterpret_cast<const uint16_t *>(p + 18)); };
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw std::runtime_error("Unsupported datalink type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start live packet capture.
|
* Start live packet capture.
|
||||||
*
|
*
|
||||||
* Steps:
|
* Steps:
|
||||||
* 1. Resolve network mask
|
* 1. Resolve network mask
|
||||||
* 2. Open device in promiscuous mode
|
* 2. Open device in promiscuous mode
|
||||||
* 3. Compile and apply BPF filter (if provided)
|
* 3. Compile and apply BPF filter (if provided)
|
||||||
* 4. Start pcap_loop in a separate thread
|
* 4. Start pcap_loop in a separate thread
|
||||||
*/
|
*/
|
||||||
void PcapCapture::start() {
|
void PcapCapture::start() {
|
||||||
// getting the netmask of the interface
|
// getting the netmask of the interface
|
||||||
if (pcap_lookupnet(interface.c_str(), &net, &mask, errbuf) == -1) {
|
if (pcap_lookupnet(interface.c_str(), &net, &mask, errbuf) == -1) {
|
||||||
fprintf(stderr, "Couldn't get netmask for device %s: %s\n",
|
fprintf(stderr, "Couldn't get netmask for device %s: %s\n", interface.c_str(), errbuf);
|
||||||
interface.c_str(), errbuf);
|
|
||||||
net = 0;
|
net = 0;
|
||||||
mask = 0;
|
mask = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* open capture device */
|
/* open capture device */
|
||||||
handle = pcap_open_live(interface.c_str(), SNAP_LEN, 1, 1000, errbuf);
|
handle.reset(pcap_open_live(interface.c_str(), SNAP_LEN, 1, 1000, errbuf));
|
||||||
if (handle == nullptr) {
|
if (handle == nullptr) {
|
||||||
fprintf(stderr, "Couldn't open device %s: %s\n", interface.c_str(), errbuf);
|
throw std::runtime_error("Couldn't open device " + interface + ": " + errbuf);
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pcap_datalink(handle) != DLT_EN10MB) {
|
datalink_type(pcap_datalink(handle.get()));
|
||||||
fprintf(stderr, "%s is not an Ethernet\n", interface.c_str());
|
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
if (!filter_exp.empty()) {
|
if (!filter_exp.empty()) {
|
||||||
/* compile the filter expression */
|
/* compile the filter expression */
|
||||||
if (pcap_compile(handle, &fp, filter_exp.c_str(), 0, net) == -1) {
|
if (pcap_compile(handle.get(), &fp, filter_exp.c_str(), 0, net) == -1) {
|
||||||
fprintf(stderr, "Couldn't parse filter %s: %s\n",
|
throw std::runtime_error("Couldn't parse filter " + filter_exp + ": " + pcap_geterr(handle.get()));
|
||||||
filter_exp.c_str(), pcap_geterr(handle));
|
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* apply the compiled filter */
|
/* apply the compiled filter */
|
||||||
if (pcap_setfilter(handle, &fp) == -1) {
|
if (pcap_setfilter(handle.get(), &fp) == -1) {
|
||||||
fprintf(stderr, "Couldn't install filter %s: %s\n",
|
throw std::runtime_error("Couldn't install filter " + filter_exp + ": " + pcap_geterr(handle.get()));
|
||||||
filter_exp.c_str(), pcap_geterr(handle));
|
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
}
|
}
|
||||||
pcap_freecode(&fp);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* start a separate thread */
|
/* start a separate thread */
|
||||||
running = true;
|
running = true;
|
||||||
thread = std::thread([this]() {
|
thread = std::thread([this]() {
|
||||||
if (pcap_loop(handle, num_packets, &PcapCapture::callback, reinterpret_cast<u_char*>(this)) < 0) {
|
if (pcap_loop(handle.get(), num_packets, &PcapCapture::callback, reinterpret_cast<u_char *>(this)) < 0) {
|
||||||
//fprintf(stderr, "Error in pcap_loop: %s\n", pcap_geterr(handle));
|
// fprintf(stderr, "Error in pcap_loop: %s\n", pcap_geterr(handle));
|
||||||
//pcap_close(handle);
|
// pcap_close(handle);
|
||||||
|
// throw std::runtime_error("Couldn't start capture");
|
||||||
}
|
}
|
||||||
running = false;
|
running = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
PcapCapture::~PcapCapture() { stop(); }
|
||||||
void PcapCapture::stop() {
|
void PcapCapture::stop() {
|
||||||
if (interfaces) {
|
pcap_freecode(&fp);
|
||||||
pcap_freealldevs(interfaces);
|
if (!handle)
|
||||||
}
|
return;
|
||||||
if (handle) {
|
|
||||||
if (running == true) pcap_breakloop(handle);
|
|
||||||
pcap_close(handle);
|
|
||||||
//handle = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (thread.joinable()) {
|
running = false;
|
||||||
|
|
||||||
|
pcap_breakloop(handle.get());
|
||||||
|
|
||||||
|
if (thread.joinable())
|
||||||
thread.join();
|
thread.join();
|
||||||
}
|
|
||||||
/*if (!filter_exp.empty()) {
|
|
||||||
pcap_freecode(&fp);
|
|
||||||
}*/
|
|
||||||
|
|
||||||
|
handle.reset();
|
||||||
|
|
||||||
|
if (interfaces) {
|
||||||
|
pcap_freealldevs(interfaces);
|
||||||
|
interfaces = nullptr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* print all available interfaces */
|
/* print all available interfaces */
|
||||||
void PcapCapture::print_interfaces() {
|
void PcapCapture::print_interfaces() {
|
||||||
int i = 0;
|
int i = 0;
|
||||||
for (pcap_if_t *dev = interfaces; dev; dev = dev->next) {
|
for (pcap_if_t *dev = interfaces; dev; dev = dev->next) {
|
||||||
printf("%d. %s ",++i, dev->name);
|
printf("%d. %s ", ++i, dev->name);
|
||||||
if (dev->description) {
|
if (dev->description) {
|
||||||
printf("(%s)\n", dev->description);
|
printf("(%s)\n", dev->description);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
printf("\n");
|
printf("\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -111,13 +129,10 @@ void PcapCapture::print_interfaces() {
|
||||||
* @param header Packet metadata
|
* @param header Packet metadata
|
||||||
* @param packet Raw packet bytes
|
* @param packet Raw packet bytes
|
||||||
*/
|
*/
|
||||||
void PcapCapture::callback(
|
void PcapCapture::callback(u_char *user, const struct pcap_pkthdr *header, const u_char *packet) {
|
||||||
u_char* user,
|
auto *self = reinterpret_cast<PcapCapture *>(user);
|
||||||
const struct pcap_pkthdr* header,
|
if (!self->isRunning())
|
||||||
const u_char* packet
|
return;
|
||||||
) {
|
|
||||||
auto* self = reinterpret_cast<PcapCapture*>(user);
|
|
||||||
if (!self->isRunning()) return;
|
|
||||||
self->got_packet(header, packet);
|
self->got_packet(header, packet);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -135,49 +150,41 @@ void PcapCapture::callback(
|
||||||
* Other Ethernet types are ignored.
|
* Other Ethernet types are ignored.
|
||||||
*/
|
*/
|
||||||
void PcapCapture::got_packet(const struct pcap_pkthdr *header, const u_char *packet) {
|
void PcapCapture::got_packet(const struct pcap_pkthdr *header, const u_char *packet) {
|
||||||
if (!running) return;
|
if (!running)
|
||||||
|
return;
|
||||||
|
|
||||||
// --- Ethernet header ---
|
// --- Ethernet header ---
|
||||||
const auto* ethernet = reinterpret_cast<const ether_header*>(packet);
|
// const auto* ethernet = reinterpret_cast<const ether_header*>(packet + offset);
|
||||||
uint16_t ether_type = ntohs(ethernet->ether_type);
|
uint16_t ether_type = get_ether_type(packet);
|
||||||
|
|
||||||
Packet packetView;
|
|
||||||
|
|
||||||
/* if we have a ipv4 type */
|
/* if we have a ipv4 type */
|
||||||
if (ether_type == ETHERTYPE_IP) {
|
if (ether_type == ETHERTYPE_IP) {
|
||||||
IPv4 ip(packet + sizeof(struct ether_header));
|
IPv4 ip(packet + offset);
|
||||||
TransportProtocol prot = ip.get_protocol();
|
TransportProtocol prot = ip.get_protocol();
|
||||||
|
|
||||||
packetView = Packet(v4, prot, ip.get_source(), ip.get_dest(), ip.get_src_port(), ip.get_dest_port(), header->len, ip.get_payload_len(), ip.get_payload_ptr());
|
Packet packetView(v4, prot, ip.get_source(), ip.get_dest(), ip.get_src_port(), ip.get_dest_port(), header->len,
|
||||||
|
ip.get_payload_len(), ip.get_payload_ptr());
|
||||||
stats->add_packet(packetView);
|
stats->add_packet(packetView);
|
||||||
stats->push(packetView);
|
stats->push(packetView);
|
||||||
|
|
||||||
}
|
}
|
||||||
/* ipv6 type */
|
/* ipv6 type */
|
||||||
if (ether_type == ETHERTYPE_IPV6) {
|
else if (ether_type == ETHERTYPE_IPV6) {
|
||||||
IPv6 ip(packet + sizeof(struct ether_header));
|
IPv6 ip(packet + offset);
|
||||||
TransportProtocol prot = ip.get_protocol();
|
TransportProtocol prot = ip.get_protocol();
|
||||||
packetView = Packet(v6, prot, ip.get_source(), ip.get_dest(), ip.get_src_port(), ip.get_dest_port(), header->len, ip.get_payload_len(), ip.get_payload_ptr());
|
Packet packetView(v6, prot, ip.get_source(), ip.get_dest(), ip.get_src_port(), ip.get_dest_port(), header->len,
|
||||||
|
ip.get_payload_len(), ip.get_payload_ptr());
|
||||||
stats->add_packet(packetView);
|
stats->add_packet(packetView);
|
||||||
stats->push(packetView);
|
stats->push(packetView);
|
||||||
|
|
||||||
}
|
|
||||||
if (ether_type == ETHERTYPE_VLAN) {
|
|
||||||
ethernet = (ether_header*)(packet + 4);
|
|
||||||
|
|
||||||
}
|
|
||||||
if (ether_type == ETHERTYPE_ARP) {
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PcapCapture::set_capabilities(std::string& interface, int num_packets, std::string& filter_exp, int packets_limit, Stats* stats) {
|
void PcapCapture::set_capabilities(const std::string &interface, int num_packets, const std::string &filter_exp,
|
||||||
|
const int packets_limit, Stats *stats) {
|
||||||
this->interface = interface;
|
this->interface = interface;
|
||||||
this->num_packets = num_packets;
|
this->num_packets = num_packets;
|
||||||
this->filter_exp = filter_exp;
|
this->filter_exp = filter_exp;
|
||||||
this->stats = stats;
|
this->stats = stats;
|
||||||
this->stats->set_packets_limit(packets_limit);
|
this->stats->set_packets_limit(packets_limit);
|
||||||
|
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* @brief Processes packets from an offline .pcap file.
|
* @brief Processes packets from an offline .pcap file.
|
||||||
|
|
@ -189,18 +196,17 @@ void PcapCapture::set_capabilities(std::string& interface, int num_packets, std:
|
||||||
*
|
*
|
||||||
* Used for post-capture analysis and exporting results.
|
* Used for post-capture analysis and exporting results.
|
||||||
*/
|
*/
|
||||||
void PcapCapture::start_offline(std::string fpath) {
|
void PcapCapture::start_offline(const std::string &fpath) {
|
||||||
handle = pcap_open_offline(fpath.c_str(), errbuf);
|
handle.reset(pcap_open_offline(fpath.c_str(), errbuf));
|
||||||
if (handle == nullptr) {
|
if (handle == nullptr) {
|
||||||
fprintf(stderr, "Error opening offline file: %s\n", errbuf);
|
fprintf(stderr, "Error opening offline file: %s\n", errbuf);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
datalink_type(pcap_datalink(handle.get()));
|
||||||
|
|
||||||
running = true;
|
running = true;
|
||||||
|
|
||||||
pcap_loop(handle, num_packets,
|
pcap_loop(handle.get(), num_packets, &PcapCapture::callback, reinterpret_cast<u_char *>(this));
|
||||||
&PcapCapture::callback,
|
|
||||||
reinterpret_cast<u_char*>(this));
|
|
||||||
|
|
||||||
running = false;
|
running = false;
|
||||||
}
|
}
|
||||||
|
|
@ -1,64 +1,54 @@
|
||||||
#include "../../include/cli/argsParse.hpp"
|
#include "../../include/cli/argsParse.hpp"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
argsParser::argsParser(int argc, char** argv) {
|
argsParser::argsParser(int argc, char **argv) {
|
||||||
desc.add_options()
|
desc.add_options()("help,h", "Display this help message and exit")("interfaces, interfaces",
|
||||||
("help,h", "Display this help message and exit")
|
"Display all possible interfaces")(
|
||||||
("interfaces, interfaces","Display all possible interfaces")
|
"interface,i", po::value<std::string>()->default_value("wlan0"),
|
||||||
("interface,i", po::value<std::string>()->default_value("wlan0"),
|
"Network interface to capture packets from (e.g. eth0, wlan0, any)")
|
||||||
"Network interface to capture packets from (e.g. eth0, wlan0, any)")
|
|
||||||
|
|
||||||
("count,c", po::value<int>()->default_value(0),
|
("count,c", po::value<int>()->default_value(0), "Number of packets to capture (0 = unlimited)")(
|
||||||
"Number of packets to capture (0 = unlimited)")
|
"time, t", po::value<int>()->default_value(INT_MAX), "Working time (in seconds)")
|
||||||
("time, t", po::value<int>()->default_value(INT_MAX),"Working time (in seconds)")
|
|
||||||
|
|
||||||
("offline,r", po::value<std::string>(),
|
("offline,r", po::value<std::string>(), "Read packets from an offline pcap file")
|
||||||
"Read packets from an offline pcap file")
|
|
||||||
|
|
||||||
("filter,f", po::value<std::vector<std::string>>()->composing(),
|
("filter,f", po::value<std::vector<std::string>>()->composing(),
|
||||||
"Traffic filter (can be used multiple times)\n"
|
"Traffic filter (can be used multiple times)\n"
|
||||||
" proto:<name> tcp | udp | icmp | dns\n"
|
" proto:<name> tcp | udp | icmp | dns\n"
|
||||||
" src:<ip> Source IP address\n"
|
" src:<ip> Source IP address\n"
|
||||||
" dst:<ip> Destination IP address\n"
|
" dst:<ip> Destination IP address\n"
|
||||||
" port:<number> Source or destination port")
|
" port:<number> Source or destination port")
|
||||||
|
|
||||||
("sort,s", po::value<std::string>()->default_value("bytes"),
|
("sort,s", po::value<std::string>()->default_value("bytes"), "Sort field: bytes | packets | ip")
|
||||||
"Sort field: bytes | packets | ip")
|
|
||||||
|
|
||||||
("order,o", po::value<std::string>()->default_value("desc"),
|
("order,o", po::value<std::string>()->default_value("desc"), "Sort order: asc | desc")
|
||||||
"Sort order: asc | desc")
|
|
||||||
|
|
||||||
("limit,n", po::value<int>()->default_value(43),
|
("limit,n", po::value<int>()->default_value(43), "Limit number of displayed entries")
|
||||||
"Limit number of displayed entries")
|
|
||||||
|
|
||||||
("csv", po::value<std::string>(),
|
("csv", po::value<std::string>(), "Export analysis results to CSV file")
|
||||||
"Export analysis results to CSV file")
|
|
||||||
|
|
||||||
("json", po::value<std::string>(),
|
("json", po::value<std::string>(), "Export analysis results to JSON file");
|
||||||
"Export analysis results to JSON file");
|
|
||||||
|
|
||||||
po::store(po::parse_command_line(argc, argv, desc), vm);
|
po::store(po::parse_command_line(argc, argv, desc), vm);
|
||||||
po::notify(vm);
|
po::notify(vm);
|
||||||
}
|
}
|
||||||
|
|
||||||
void argsParser::print_help() const {
|
void argsParser::print_help() const {
|
||||||
std::cout <<
|
std::cout << "Network Traffic Analyzer\n"
|
||||||
"Network Traffic Analyzer\n"
|
"========================\n\n"
|
||||||
"========================\n\n"
|
"Usage:\n"
|
||||||
"Usage:\n"
|
" ./network-traffic-analyzer [options]\n\n"
|
||||||
" ./network-traffic-analyzer [options]\n\n"
|
"Description:\n"
|
||||||
"Description:\n"
|
" Captures and analyzes network traffic from live interfaces or\n"
|
||||||
" Captures and analyzes network traffic from live interfaces or\n"
|
" offline pcap files. Provides protocol statistics, top talkers,\n"
|
||||||
" offline pcap files. Provides protocol statistics, top talkers,\n"
|
" and bandwidth usage information.\n\n";
|
||||||
" and bandwidth usage information.\n\n";
|
|
||||||
|
|
||||||
std::cout << desc << "\n";
|
std::cout << desc << "\n";
|
||||||
|
|
||||||
std::cout <<
|
std::cout << "Examples:\n"
|
||||||
"Examples:\n"
|
" ./network-traffic-analyzer -i wlan0 --count 100 --time 10\n"
|
||||||
" ./network-traffic-analyzer -i wlan0 --count 100 --time 10\n"
|
" ./network-traffic-analyzer -i any --filter port:54\n"
|
||||||
" ./network-traffic-analyzer -i any --filter port:54\n"
|
" ./network-traffic-analyzer --offline traffic.pcap --json result.json\n\n";
|
||||||
" ./network-traffic-analyzer --offline traffic.pcap --json result.json\n\n";
|
|
||||||
|
|
||||||
std::cout << "To end the program, press 'q' or Esc to exit.\n";
|
std::cout << "To end the program, press 'q' or Esc to exit.\n";
|
||||||
}
|
}
|
||||||
|
|
@ -1,60 +1,81 @@
|
||||||
#include "../../include/cli/filter.hpp"
|
#include "../../include/cli/filter.hpp"
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
filter parse(std::string str) {
|
filter parse(const std::string &str) {
|
||||||
auto pos = str.find(':');
|
auto pos = str.find(':');
|
||||||
if (pos == std::string::npos) {
|
if (pos == std::string::npos) {
|
||||||
|
throw std::invalid_argument("Invalid filter format: '" + str + "' (expected key:value)");
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string type = str.substr(0, pos);
|
std::string type = str.substr(0, pos);
|
||||||
std::string value = str.substr(pos + 1);
|
std::string value = str.substr(pos + 1);
|
||||||
|
|
||||||
if (type == "protocol") return {PROTOCOL, value};
|
if (type == "protocol")
|
||||||
if (type == "port") return {PORT, value};
|
return {PROTOCOL, value};
|
||||||
if (type == "dest") return {IP_DEST, value};
|
if (type == "port")
|
||||||
if (type == "src") return {IP_SRC, value};
|
return {PORT, value};
|
||||||
if (type == "ip") return {IP_TYPE, value};
|
if (type == "dest")
|
||||||
|
return {IP_DEST, value};
|
||||||
|
if (type == "src")
|
||||||
|
return {IP_SRC, value};
|
||||||
|
if (type == "ip")
|
||||||
|
return {IP_TYPE, value};
|
||||||
return {NONE, value};
|
return {NONE, value};
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string get_bpf_filter(std::vector<filter>& f) {
|
std::string get_bpf_filter(const std::vector<filter> &f) {
|
||||||
std::map<filter_type, std::vector<std::string>> groups;
|
std::map<filter_type, std::vector<std::string>> groups;
|
||||||
|
|
||||||
for (const auto& x : f) {
|
for (const auto &x : f) {
|
||||||
switch (x.type) {
|
switch (x.type) {
|
||||||
case PROTOCOL:
|
case PROTOCOL:
|
||||||
if (x.val == "dns")
|
if (x.val == "dns")
|
||||||
groups[PROTOCOL].push_back("port 53");
|
groups[PROTOCOL].emplace_back("port 53");
|
||||||
else
|
else if (x.val == "http")
|
||||||
groups[PROTOCOL].push_back(x.val);
|
groups[PROTOCOL].emplace_back("port 80");
|
||||||
break;
|
else if (x.val == "https")
|
||||||
|
groups[PROTOCOL].emplace_back("port 443");
|
||||||
|
else if (x.val == "ssh")
|
||||||
|
groups[PROTOCOL].emplace_back("port 22");
|
||||||
|
else if (x.val == "ftp")
|
||||||
|
groups[PROTOCOL].emplace_back("port 21");
|
||||||
|
else if (x.val == "smtp")
|
||||||
|
groups[PROTOCOL].emplace_back("port 25");
|
||||||
|
else
|
||||||
|
groups[PROTOCOL].push_back(x.val);
|
||||||
|
break;
|
||||||
|
|
||||||
case IP_DEST:
|
case IP_DEST:
|
||||||
groups[IP_DEST].push_back("dst host " + x.val);
|
groups[IP_DEST].push_back("dst host " + x.val);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case IP_SRC:
|
case IP_SRC:
|
||||||
groups[IP_SRC].push_back("src host " + x.val);
|
groups[IP_SRC].push_back("src host " + x.val);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case PORT:
|
case PORT:
|
||||||
groups[PORT].push_back("port " + x.val);
|
groups[PORT].push_back("port " + x.val);
|
||||||
break;
|
break;
|
||||||
case IP_TYPE:
|
case IP_TYPE: {
|
||||||
groups[IP_TYPE].push_back(x.val);
|
if (x.val == "v4" || x.val == "4" || x.val == "ipv4")
|
||||||
break;
|
groups[IP_TYPE].emplace_back("ip");
|
||||||
|
else if (x.val == "v6" || x.val == "6" || x.val == "ipv6")
|
||||||
|
groups[IP_TYPE].emplace_back("ip6");
|
||||||
|
else
|
||||||
|
throw std::invalid_argument("Unknown IP type: '" + x.val + "'");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string result;
|
std::string result;
|
||||||
bool first_group = true;
|
bool first_group = true;
|
||||||
|
|
||||||
for (auto& [type, parts] : groups) {
|
for (auto &[type, parts] : groups) {
|
||||||
if (!first_group)
|
if (!first_group)
|
||||||
result += " and ";
|
result += " and ";
|
||||||
first_group = false;
|
first_group = false;
|
||||||
|
|
@ -74,4 +95,3 @@ std::string get_bpf_filter(std::vector<filter>& f) {
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,211 +1,176 @@
|
||||||
#include "../../include/packet/IP.hpp"
|
#include "../../include/packet/IP.hpp"
|
||||||
|
|
||||||
#include <cstdio>
|
|
||||||
#include <arpa/inet.h>
|
#include <arpa/inet.h>
|
||||||
|
#include <array>
|
||||||
|
#include <cstdio>
|
||||||
#include <netinet/icmp6.h>
|
#include <netinet/icmp6.h>
|
||||||
#include <netinet/ip_icmp.h>
|
#include <netinet/ip_icmp.h>
|
||||||
#include <netinet/tcp.h>
|
#include <netinet/tcp.h>
|
||||||
#include <netinet/udp.h>
|
#include <netinet/udp.h>
|
||||||
|
#include <stdexcept>
|
||||||
|
uint16_t IP_class::get_payload_len() const { return payload_len; }
|
||||||
|
|
||||||
uint16_t IP_class::get_payload_len() {
|
TransportProtocol IP_class::get_protocol() const { return protocol; }
|
||||||
return payload_len;
|
std::string IP_class::get_source() { return src; }
|
||||||
}
|
std::string IP_class::get_dest() { return dst; }
|
||||||
|
|
||||||
|
|
||||||
/*** Ipv4 ***/
|
/*** Ipv4 ***/
|
||||||
IPv4::IPv4(const u_char* data) {
|
IPv4::IPv4(const u_char *data) {
|
||||||
ip_hdr = reinterpret_cast<const ip*>(data);
|
ip_hdr = reinterpret_cast<const ip *>(data);
|
||||||
|
|
||||||
|
src = inet_ntoa(ip_hdr->ip_src);
|
||||||
|
dst = inet_ntoa(ip_hdr->ip_dst);
|
||||||
|
|
||||||
ip_hdr_len = ip_hdr->ip_hl * 4;
|
ip_hdr_len = ip_hdr->ip_hl * 4;
|
||||||
if (ip_hdr_len < 20) {
|
if (ip_hdr_len < 20) {
|
||||||
fprintf(stderr, "Failed to initial IPv4 ");
|
throw std::runtime_error("Failed to initial IPv4 ");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
TransportProtocol IPv4::get_protocol() {
|
|
||||||
|
|
||||||
switch (ip_hdr->ip_p) {
|
switch (ip_hdr->ip_p) {
|
||||||
case IPPROTO_TCP:
|
case IPPROTO_TCP:
|
||||||
handle_tcp();
|
IPv4::handle_tcp();
|
||||||
return TransportProtocol::TCP;
|
break;
|
||||||
|
case IPPROTO_UDP:
|
||||||
|
IPv4::handle_udp();
|
||||||
|
break;
|
||||||
|
|
||||||
case IPPROTO_UDP:
|
case IPPROTO_ICMP:
|
||||||
handle_udp();
|
IPv4::handle_icmp();
|
||||||
return TransportProtocol::UDP;
|
break;
|
||||||
|
|
||||||
case IPPROTO_ICMP:
|
case IPPROTO_ICMPV6:
|
||||||
handle_icmp();
|
IPv4::handle_icmpv6();
|
||||||
return TransportProtocol::ICMP;
|
break;
|
||||||
|
case IPPROTO_IGMP:
|
||||||
case IPPROTO_ICMPV6:
|
IPv4::handle_igmp();
|
||||||
handle_icmpv6();
|
break;
|
||||||
return TransportProtocol::ICMP6;
|
default:
|
||||||
|
protocol = TransportProtocol::UNKNOWN;
|
||||||
case IPPROTO_IGMP:
|
break;
|
||||||
handle_igmp();
|
|
||||||
return TransportProtocol::IGMP;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return TransportProtocol::UNKNOWN;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void IPv4::handle_tcp() {
|
void IPv4::handle_tcp() {
|
||||||
auto* tcp = reinterpret_cast<tcphdr*>((u_char*)ip_hdr + ip_hdr_len);
|
const auto *tcp = reinterpret_cast<const tcphdr *>(reinterpret_cast<const u_char *>(ip_hdr) + ip_hdr_len);
|
||||||
|
|
||||||
src_port = ntohs(tcp->source);
|
src_port = ntohs(tcp->source);
|
||||||
dest_port = ntohs(tcp->dest);
|
dest_port = ntohs(tcp->dest);
|
||||||
|
|
||||||
payload_ptr = reinterpret_cast<u_char*>(tcp + tcp->doff * 4);
|
payload_ptr = reinterpret_cast<const u_char *>(tcp) + tcp->doff * 4;
|
||||||
|
|
||||||
payload_len = ntohs(ip_hdr->ip_len) - (ip_hdr_len + tcp->doff * 4);
|
payload_len = ntohs(ip_hdr->ip_len) - (ip_hdr_len + tcp->doff * 4);
|
||||||
|
|
||||||
|
protocol = TransportProtocol::TCP;
|
||||||
}
|
}
|
||||||
void IPv4::handle_udp() {
|
void IPv4::handle_udp() {
|
||||||
auto* udp = reinterpret_cast<udphdr*>((u_char*)ip_hdr + ip_hdr_len);
|
const auto *udp = reinterpret_cast<const udphdr *>(reinterpret_cast<const u_char *>(ip_hdr) + ip_hdr_len);
|
||||||
dest_port = ntohs(udp->dest);
|
dest_port = ntohs(udp->dest);
|
||||||
src_port = ntohs(udp->source);
|
src_port = ntohs(udp->source);
|
||||||
|
|
||||||
payload_ptr = (u_char*)udp + sizeof(udphdr);
|
payload_ptr = reinterpret_cast<const u_char *>(udp) + sizeof(udphdr);
|
||||||
payload_len = ntohs(udp->len) - sizeof(udphdr);
|
payload_len = ntohs(udp->len) - sizeof(udphdr);
|
||||||
}
|
|
||||||
void IPv4::handle_icmp() {
|
|
||||||
auto* icmp = reinterpret_cast<icmphdr*>((u_char*)ip_hdr + ip_hdr_len);
|
|
||||||
|
|
||||||
|
protocol = TransportProtocol::UDP;
|
||||||
|
}
|
||||||
|
void IPv4::handle_icmp() { protocol = TransportProtocol::ICMP; }
|
||||||
|
void IPv4::handle_icmpv6() { protocol = TransportProtocol::ICMP6; }
|
||||||
|
void IPv4::handle_igmp() { protocol = TransportProtocol::IGMP; }
|
||||||
|
|
||||||
}
|
uint16_t IPv4::get_src_port() { return src_port; }
|
||||||
void IPv4::handle_icmpv6() {
|
uint16_t IPv4::get_dest_port() { return dest_port; }
|
||||||
auto* icmp = reinterpret_cast<icmp6_hdr*>((u_char*)ip_hdr + ip_hdr_len);
|
|
||||||
|
|
||||||
}
|
|
||||||
void IPv4::handle_igmp() {
|
|
||||||
auto* igmp = reinterpret_cast<struct igmp*>((u_char*)ip_hdr + ip_hdr_len);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string IPv4::get_source() {
|
|
||||||
return std::string(inet_ntoa(ip_hdr->ip_src));
|
|
||||||
}
|
|
||||||
std::string IPv4::get_dest() {
|
|
||||||
return std::string(inet_ntoa(ip_hdr->ip_dst));
|
|
||||||
}
|
|
||||||
uint16_t IPv4::get_src_port() {
|
|
||||||
return src_port;
|
|
||||||
}
|
|
||||||
uint16_t IPv4::get_dest_port() {
|
|
||||||
return dest_port;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*** Ipv6 ***/
|
/*** Ipv6 ***/
|
||||||
|
|
||||||
IPv6::IPv6(const u_char* data) {
|
IPv6::IPv6(const u_char *data) {
|
||||||
ip_hdr = reinterpret_cast<const ip6_hdr*>(data);
|
ip_hdr = reinterpret_cast<const ip6_hdr *>(data);
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
TransportProtocol IPv6::get_protocol() {
|
|
||||||
uint8_t hdr = ip_hdr->ip6_nxt;
|
uint8_t hdr = ip_hdr->ip6_nxt;
|
||||||
|
std::array<char, INET6_ADDRSTRLEN> src{};
|
||||||
|
inet_ntop(AF_INET6, &ip_hdr->ip6_src, src.data(), sizeof(src));
|
||||||
|
this->src = src.data();
|
||||||
|
|
||||||
ptr = reinterpret_cast<const uint8_t*>(ip_hdr + 1);
|
std::array<char, INET6_ADDRSTRLEN> dst{};
|
||||||
|
inet_ntop(AF_INET6, &ip_hdr->ip6_dst, dst.data(), sizeof(dst));
|
||||||
|
this->dst = dst.data();
|
||||||
|
|
||||||
|
ptr = reinterpret_cast<const uint8_t *>(ip_hdr + 1);
|
||||||
while (true) {
|
while (true) {
|
||||||
switch (hdr) {
|
switch (hdr) {
|
||||||
case IPPROTO_TCP:
|
case IPPROTO_TCP:
|
||||||
handle_tcp();
|
IPv6::handle_tcp();
|
||||||
return TransportProtocol::TCP;
|
return;
|
||||||
|
case IPPROTO_UDP:
|
||||||
|
IPv6::handle_udp();
|
||||||
|
return;
|
||||||
|
case IPPROTO_ICMP:
|
||||||
|
IPv6::handle_icmp();
|
||||||
|
return;
|
||||||
|
case IPPROTO_ICMPV6:
|
||||||
|
IPv6::handle_icmpv6();
|
||||||
|
return;
|
||||||
|
|
||||||
case IPPROTO_UDP:
|
case IPPROTO_IGMP:
|
||||||
handle_udp();
|
IPv6::handle_igmp();
|
||||||
return TransportProtocol::UDP;
|
return;
|
||||||
|
/* if we have an extension headers, dont leave from loop,
|
||||||
|
* keep find a protocol type
|
||||||
|
*/
|
||||||
|
case IPPROTO_HOPOPTS:
|
||||||
|
case IPPROTO_ROUTING:
|
||||||
|
case IPPROTO_DSTOPTS: {
|
||||||
|
const auto *ext = reinterpret_cast<const ip6_ext *>(ptr);
|
||||||
|
hdr = ext->ip6e_nxt;
|
||||||
|
ptr += (ext->ip6e_len + 1) * 8;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case IPPROTO_FRAGMENT: {
|
||||||
|
|
||||||
case IPPROTO_ICMP:
|
const auto *frag = reinterpret_cast<const ip6_frag *>(ptr);
|
||||||
handle_icmp();
|
hdr = frag->ip6f_nxt;
|
||||||
return TransportProtocol::ICMP;
|
ptr += sizeof(ip6_frag);
|
||||||
|
break;
|
||||||
case IPPROTO_ICMPV6:
|
}
|
||||||
handle_icmpv6();
|
default:
|
||||||
return TransportProtocol::ICMP6;
|
protocol = TransportProtocol::UNKNOWN;
|
||||||
|
return;
|
||||||
case IPPROTO_IGMP:
|
|
||||||
handle_igmp();
|
|
||||||
return TransportProtocol::IGMP;
|
|
||||||
/* if we have an extension headers, dont leave from loop,
|
|
||||||
* keep find a protocol type
|
|
||||||
*/
|
|
||||||
case IPPROTO_HOPOPTS:
|
|
||||||
case IPPROTO_ROUTING:
|
|
||||||
case IPPROTO_DSTOPTS: {
|
|
||||||
const auto* ext = reinterpret_cast<const ip6_ext*>(ptr);
|
|
||||||
hdr = ext->ip6e_nxt;
|
|
||||||
ptr += (ext->ip6e_len + 1) * 8;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case IPPROTO_FRAGMENT: {
|
|
||||||
|
|
||||||
const auto* frag = reinterpret_cast<const ip6_frag*>(ptr);
|
|
||||||
hdr = frag->ip6f_nxt;
|
|
||||||
ptr += sizeof(ip6_frag);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return TransportProtocol::UNKNOWN;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ptr = nullptr;
|
ptr = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void IPv6::handle_tcp() {
|
void IPv6::handle_tcp() {
|
||||||
const auto tcp = reinterpret_cast<const tcphdr*>(ptr);
|
const auto tcp = reinterpret_cast<const tcphdr *>(ptr);
|
||||||
dest_port = ntohs(tcp->dest);
|
dest_port = ntohs(tcp->dest);
|
||||||
src_port = ntohs(tcp->source);
|
src_port = ntohs(tcp->source);
|
||||||
|
|
||||||
payload_ptr = (const uint8_t*)tcp + tcp->doff * 4;
|
payload_ptr = reinterpret_cast<const uint8_t *>(tcp) + tcp->doff * 4;
|
||||||
payload_len = ntohs(ip_hdr->ip6_plen) - tcp->doff * 4;
|
payload_len = ntohs(ip_hdr->ip6_plen) - tcp->doff * 4;
|
||||||
|
|
||||||
|
protocol = TransportProtocol::TCP;
|
||||||
ptr = nullptr;
|
ptr = nullptr;
|
||||||
|
|
||||||
}
|
}
|
||||||
void IPv6::handle_udp() {
|
void IPv6::handle_udp() {
|
||||||
const auto udp = reinterpret_cast<const udphdr*>(ptr);
|
const auto udp = reinterpret_cast<const udphdr *>(ptr);
|
||||||
dest_port = ntohs(udp->dest);
|
dest_port = ntohs(udp->dest);
|
||||||
src_port = ntohs(udp->source);
|
src_port = ntohs(udp->source);
|
||||||
|
|
||||||
payload_ptr = (const uint8_t*)udp + sizeof(udphdr);
|
payload_ptr = reinterpret_cast<const uint8_t *>(udp) + sizeof(udphdr);
|
||||||
payload_len = ntohs(udp->len) - sizeof(udphdr);
|
payload_len = ntohs(udp->len) - sizeof(udphdr);
|
||||||
|
|
||||||
|
protocol = TransportProtocol::UDP;
|
||||||
ptr = nullptr;
|
ptr = nullptr;
|
||||||
|
|
||||||
}
|
}
|
||||||
void IPv6::handle_icmp() {
|
void IPv6::handle_icmp() {
|
||||||
|
protocol = TransportProtocol::ICMP;
|
||||||
ptr = nullptr;
|
ptr = nullptr;
|
||||||
}
|
}
|
||||||
void IPv6::handle_icmpv6() {
|
void IPv6::handle_icmpv6() {
|
||||||
|
protocol = TransportProtocol::ICMP6;
|
||||||
payload_len = ntohs(ip_hdr->ip6_plen) - sizeof(icmp6_hdr);
|
payload_len = ntohs(ip_hdr->ip6_plen) - sizeof(icmp6_hdr);
|
||||||
ptr = nullptr;
|
ptr = nullptr;
|
||||||
}
|
}
|
||||||
void IPv6::handle_igmp() {
|
void IPv6::handle_igmp() {
|
||||||
|
protocol = TransportProtocol::IGMP;
|
||||||
ptr = nullptr;
|
ptr = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string IPv6::get_source() {
|
uint16_t IPv6::get_src_port() { return src_port; }
|
||||||
char src[INET6_ADDRSTRLEN];
|
|
||||||
inet_ntop(AF_INET6, &ip_hdr->ip6_src, src, sizeof(src));
|
|
||||||
return std::string(src);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string IPv6::get_dest() {
|
|
||||||
char dst[INET6_ADDRSTRLEN];
|
|
||||||
inet_ntop(AF_INET6, &ip_hdr->ip6_dst, dst, sizeof(dst));
|
|
||||||
return std::string(dst);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t IPv6::get_src_port() {
|
|
||||||
return src_port;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t IPv6::get_dest_port() {
|
|
||||||
return dest_port;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
uint16_t IPv6::get_dest_port() { return dest_port; }
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,12 @@
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
ApplicationProtocol Packet::get_application_protocol() {
|
ApplicationProtocol Packet::get_application_protocol() {
|
||||||
if (!payload_ptr || payload_len < 4) goto check_port;
|
if (!payload_ptr || payload_len < 4)
|
||||||
|
goto check_port;
|
||||||
|
|
||||||
if (transport_protocol == TransportProtocol::TCP) {
|
if (transport_protocol == TransportProtocol::TCP) {
|
||||||
if (!memcmp(payload_ptr, "GET ", 4) ||
|
if (!memcmp(payload_ptr, "GET ", 4) || !memcmp(payload_ptr, "POST", 4) || !memcmp(payload_ptr, "HEAD", 4) ||
|
||||||
!memcmp(payload_ptr, "POST", 4) ||
|
!memcmp(payload_ptr, "PUT ", 4) || !memcmp(payload_ptr, "HTTP", 4))
|
||||||
!memcmp(payload_ptr, "HEAD", 4) ||
|
|
||||||
!memcmp(payload_ptr, "PUT ", 4) ||
|
|
||||||
!memcmp(payload_ptr, "HTTP", 4))
|
|
||||||
return ApplicationProtocol::HTTP;
|
return ApplicationProtocol::HTTP;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -25,25 +23,35 @@ check_port:
|
||||||
uint16_t port = (src_port < dst_port) ? src_port : dst_port;
|
uint16_t port = (src_port < dst_port) ? src_port : dst_port;
|
||||||
if (transport_protocol == TransportProtocol::TCP) {
|
if (transport_protocol == TransportProtocol::TCP) {
|
||||||
switch (port) {
|
switch (port) {
|
||||||
case 21: return ApplicationProtocol::FTP;
|
case 21:
|
||||||
case 22: return ApplicationProtocol::SSH;
|
return ApplicationProtocol::FTP;
|
||||||
case 25: return ApplicationProtocol::SMTP;
|
case 22:
|
||||||
case 53: return ApplicationProtocol::DNS;
|
return ApplicationProtocol::SSH;
|
||||||
case 80: return ApplicationProtocol::HTTP;
|
case 25:
|
||||||
case 443: return ApplicationProtocol::HTTPS;
|
return ApplicationProtocol::SMTP;
|
||||||
default: return ApplicationProtocol::UNKNOWN;
|
case 53:
|
||||||
|
return ApplicationProtocol::DNS;
|
||||||
|
case 80:
|
||||||
|
return ApplicationProtocol::HTTP;
|
||||||
|
case 443:
|
||||||
|
return ApplicationProtocol::HTTPS;
|
||||||
|
default:
|
||||||
|
return ApplicationProtocol::UNKNOWN;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (transport_protocol == TransportProtocol::UDP) {
|
if (transport_protocol == TransportProtocol::UDP) {
|
||||||
switch (port) {
|
switch (port) {
|
||||||
case 53: return ApplicationProtocol::DNS;
|
case 53:
|
||||||
case 443: return ApplicationProtocol::QUIC;
|
return ApplicationProtocol::DNS;
|
||||||
case 123: return ApplicationProtocol::NTP;
|
case 443:
|
||||||
default: return ApplicationProtocol::UNKNOWN;
|
return ApplicationProtocol::QUIC;
|
||||||
|
case 123:
|
||||||
|
return ApplicationProtocol::NTP;
|
||||||
|
default:
|
||||||
|
return ApplicationProtocol::UNKNOWN;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ApplicationProtocol::UNKNOWN;
|
return ApplicationProtocol::UNKNOWN;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,8 @@
|
||||||
#include "../../include/stats/protocolStats.hpp"
|
#include "../../include/stats/protocolStats.hpp"
|
||||||
|
|
||||||
#include "ftxui/dom/table.hpp"
|
#include "ftxui/dom/table.hpp"
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
Stats::Stats() { last_tick = std::chrono::steady_clock::now(); }
|
||||||
Stats::Stats() {
|
|
||||||
last_tick = std::chrono::steady_clock::now();
|
|
||||||
}
|
|
||||||
/**
|
/**
|
||||||
* @brief Aggregates a newly captured packet.
|
* @brief Aggregates a newly captured packet.
|
||||||
*
|
*
|
||||||
|
|
@ -19,17 +16,17 @@ Stats::Stats() {
|
||||||
* Must be called only from capture thread.
|
* Must be called only from capture thread.
|
||||||
* Protected by mutex.
|
* Protected by mutex.
|
||||||
*/
|
*/
|
||||||
void Stats::add_packet(Packet &packet) {
|
void Stats::add_packet(const Packet &packet) {
|
||||||
std::lock_guard<std::mutex> lock(mtx);
|
std::lock_guard<std::mutex> lock(mtx);
|
||||||
|
|
||||||
++snapshot.total_p;
|
++snapshot.total_p;
|
||||||
snapshot.total_b += packet.total_len;
|
snapshot.total_b += packet.total_len;
|
||||||
|
|
||||||
auto& t = transport_map[packet.transport_protocol];
|
auto &t = transport_map[packet.transport_protocol];
|
||||||
t.packets++;
|
t.packets++;
|
||||||
t.bytes += packet.total_len;
|
t.bytes += packet.total_len;
|
||||||
|
|
||||||
auto& a = application_map[packet.application_protocol];
|
auto &a = application_map[packet.application_protocol];
|
||||||
a.packets++;
|
a.packets++;
|
||||||
a.bytes += packet.payload_len;
|
a.bytes += packet.payload_len;
|
||||||
|
|
||||||
|
|
@ -42,32 +39,45 @@ void Stats::add_packet(Packet &packet) {
|
||||||
auto key = std::make_pair(packet.src, packet.dst);
|
auto key = std::make_pair(packet.src, packet.dst);
|
||||||
pairs[key].packets++;
|
pairs[key].packets++;
|
||||||
pairs[key].bytes += packet.total_len;
|
pairs[key].bytes += packet.total_len;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const char *transport_to_str(TransportProtocol p) {
|
||||||
const char* transport_to_str(TransportProtocol p) {
|
|
||||||
switch (p) {
|
switch (p) {
|
||||||
case TransportProtocol::TCP: return "TCP";
|
case TransportProtocol::TCP:
|
||||||
case TransportProtocol::UDP: return "UDP";
|
return "TCP";
|
||||||
case TransportProtocol::ICMP: return "ICMP";
|
case TransportProtocol::UDP:
|
||||||
case TransportProtocol::ICMP6: return "ICMP6";
|
return "UDP";
|
||||||
case TransportProtocol::IGMP: return "IGMP";
|
case TransportProtocol::ICMP:
|
||||||
default: return "UNKNOWN";
|
return "ICMP";
|
||||||
|
case TransportProtocol::ICMP6:
|
||||||
|
return "ICMP6";
|
||||||
|
case TransportProtocol::IGMP:
|
||||||
|
return "IGMP";
|
||||||
|
default:
|
||||||
|
return "UNKNOWN";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* app_to_str(ApplicationProtocol p) {
|
const char *app_to_str(ApplicationProtocol p) {
|
||||||
switch (p) {
|
switch (p) {
|
||||||
case ApplicationProtocol::HTTP: return "HTTP";
|
case ApplicationProtocol::HTTP:
|
||||||
case ApplicationProtocol::HTTPS: return "HTTPS";
|
return "HTTP";
|
||||||
case ApplicationProtocol::DNS: return "DNS";
|
case ApplicationProtocol::HTTPS:
|
||||||
case ApplicationProtocol::FTP: return "FTP";
|
return "HTTPS";
|
||||||
case ApplicationProtocol::SSH: return "SSH";
|
case ApplicationProtocol::DNS:
|
||||||
case ApplicationProtocol::SMTP: return "SMTP";
|
return "DNS";
|
||||||
case ApplicationProtocol::QUIC: return "QUIC";
|
case ApplicationProtocol::FTP:
|
||||||
case ApplicationProtocol::NTP: return "NTP";
|
return "FTP";
|
||||||
default: return "UNKNOWN";
|
case ApplicationProtocol::SSH:
|
||||||
|
return "SSH";
|
||||||
|
case ApplicationProtocol::SMTP:
|
||||||
|
return "SMTP";
|
||||||
|
case ApplicationProtocol::QUIC:
|
||||||
|
return "QUIC";
|
||||||
|
case ApplicationProtocol::NTP:
|
||||||
|
return "NTP";
|
||||||
|
default:
|
||||||
|
return "UNKNOWN";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -83,29 +93,19 @@ const char* app_to_str(ApplicationProtocol p) {
|
||||||
void Stats::update_transport_stats() {
|
void Stats::update_transport_stats() {
|
||||||
std::lock_guard<std::mutex> lock(mtx);
|
std::lock_guard<std::mutex> lock(mtx);
|
||||||
snapshot.transport_rows.clear();
|
snapshot.transport_rows.clear();
|
||||||
snapshot.transport_rows.push_back({ "Proto", "Packets", "Bytes", "%" });
|
snapshot.transport_rows.push_back({"Proto", "Packets", "Bytes", "%"});
|
||||||
|
|
||||||
std::vector<std::pair<TransportProtocol, protocolStats>> tps(
|
std::vector<std::pair<TransportProtocol, protocolStats>> tps(transport_map.begin(), transport_map.end());
|
||||||
transport_map.begin(), transport_map.end()
|
std::sort(tps.begin(), tps.end(), [](auto &a, auto &b) { return a.second.packets > b.second.packets; });
|
||||||
);
|
|
||||||
std::sort(tps.begin(), tps.end(),
|
|
||||||
[](auto& a, auto& b) {
|
|
||||||
return a.second.packets > b.second.packets;
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const auto& [proto, stats] : tps) {
|
for (const auto &[proto, stats] : tps) {
|
||||||
double percent = snapshot.total_b ? stats.bytes * 100.0 / snapshot.total_b : 0.0;
|
double percent = snapshot.total_b ? stats.bytes * 100.0 / snapshot.total_b : 0.0;
|
||||||
snapshot.transport_rows.push_back({
|
snapshot.transport_rows.push_back({transport_to_str(proto), std::to_string(stats.packets),
|
||||||
transport_to_str(proto),
|
std::format("{:.2f}", stats.bytes / (1024.0 * 1024.0)),
|
||||||
std::to_string(stats.packets),
|
std::format("{:.2f}", percent)});
|
||||||
std::format("{:.2f}", stats.bytes / (1024.0 * 1024.0)),
|
|
||||||
std::format("{:.2f}", percent)
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Rebuilds application protocol snapshot.
|
* @brief Rebuilds application protocol snapshot.
|
||||||
*
|
*
|
||||||
|
|
@ -114,29 +114,19 @@ void Stats::update_transport_stats() {
|
||||||
*/
|
*/
|
||||||
void Stats::update_application_stats() {
|
void Stats::update_application_stats() {
|
||||||
std::lock_guard<std::mutex> lock(mtx);
|
std::lock_guard<std::mutex> lock(mtx);
|
||||||
std::vector<std::pair<ApplicationProtocol, protocolStats>> apps(
|
std::vector<std::pair<ApplicationProtocol, protocolStats>> apps(application_map.begin(), application_map.end());
|
||||||
application_map.begin(), application_map.end()
|
|
||||||
);
|
|
||||||
|
|
||||||
std::sort(apps.begin(), apps.end(),
|
std::sort(apps.begin(), apps.end(), [](auto &a, auto &b) { return a.second.packets > b.second.packets; });
|
||||||
[](auto& a, auto& b) {
|
|
||||||
return a.second.packets > b.second.packets;
|
|
||||||
});
|
|
||||||
|
|
||||||
snapshot.app_rows.clear();
|
snapshot.app_rows.clear();
|
||||||
snapshot.app_rows.push_back({ "Proto", "Packets", "Bytes (MB)", "%" });
|
snapshot.app_rows.push_back({"Proto", "Packets", "Bytes (MB)", "%"});
|
||||||
|
|
||||||
for (const auto& [proto, s] : apps) {
|
for (const auto &[proto, s] : apps) {
|
||||||
double percent = snapshot.total_b
|
double percent = snapshot.total_b ? s.bytes * 100.0 / snapshot.total_b : 0.0;
|
||||||
? s.bytes * 100.0 / snapshot.total_b
|
|
||||||
: 0.0;
|
|
||||||
|
|
||||||
snapshot.app_rows.push_back({
|
snapshot.app_rows.push_back({app_to_str(proto), std::to_string(s.packets),
|
||||||
app_to_str(proto),
|
std::format("{:.2f}", s.bytes / (1024.0 * 1024.0)),
|
||||||
std::to_string(s.packets),
|
std::format("{:.2f}", percent)});
|
||||||
std::format("{:.2f}", s.bytes / (1024.0 * 1024.0)),
|
|
||||||
std::format("{:.2f}", percent)
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -152,23 +142,16 @@ void Stats::update_ip_stats(size_t limit) {
|
||||||
snapshot.rows.clear();
|
snapshot.rows.clear();
|
||||||
|
|
||||||
snapshot.rows.push_back({"IP Address", "Packets TX", "Packets RX"});
|
snapshot.rows.push_back({"IP Address", "Packets TX", "Packets RX"});
|
||||||
std::vector<std::pair<std::string, IPStats>> ips(
|
std::vector<std::pair<std::string, IPStats>> ips(ip_map.begin(), ip_map.end());
|
||||||
ip_map.begin(), ip_map.end()
|
|
||||||
);
|
|
||||||
|
|
||||||
std::sort(ips.begin(), ips.end(),
|
std::sort(ips.begin(), ips.end(), [](auto &a, auto &b) { return a.second.packets_sent > b.second.packets_sent; });
|
||||||
[](auto& a, auto& b) {
|
|
||||||
return a.second.packets_sent > b.second.packets_sent;
|
|
||||||
});
|
|
||||||
|
|
||||||
size_t count = 0;
|
size_t count = 0;
|
||||||
for (const auto& [ip, s] : ips) {
|
for (const auto &[ip, s] : ips) {
|
||||||
if (count++ >= limit) break;
|
if (count++ >= limit)
|
||||||
snapshot.rows.push_back({ip,
|
break;
|
||||||
"TX: " + std::to_string(s.packets_sent),
|
snapshot.rows.push_back(
|
||||||
"RX: " + std::to_string(s.packets_received)}
|
{ip, "TX: " + std::to_string(s.packets_sent), "RX: " + std::to_string(s.packets_received)});
|
||||||
);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -183,17 +166,15 @@ void Stats::update_ip_stats(size_t limit) {
|
||||||
void Stats::update_pairs(size_t limit) {
|
void Stats::update_pairs(size_t limit) {
|
||||||
std::lock_guard<std::mutex> lock(mtx);
|
std::lock_guard<std::mutex> lock(mtx);
|
||||||
std::vector<std::pair<std::pair<std::string, std::string>, protocolStats>> vec(pairs.begin(), pairs.end());
|
std::vector<std::pair<std::pair<std::string, std::string>, protocolStats>> vec(pairs.begin(), pairs.end());
|
||||||
std::sort(vec.begin(), vec.end(),
|
std::sort(vec.begin(), vec.end(), [](auto &a, auto &b) { return a.second.bytes > b.second.bytes; });
|
||||||
[](auto& a, auto& b) {
|
|
||||||
return a.second.bytes > b.second.bytes;
|
|
||||||
});
|
|
||||||
|
|
||||||
snapshot.pairs_rows.clear();
|
snapshot.pairs_rows.clear();
|
||||||
snapshot.pairs_rows.push_back({ "Source", "Destination", "bytes received", "%" });
|
snapshot.pairs_rows.push_back({"Source", "Destination", "bytes received", "%"});
|
||||||
size_t count = 0;
|
size_t count = 0;
|
||||||
for (const auto& [pair, s] : vec) {
|
for (const auto &[pair, s] : vec) {
|
||||||
|
|
||||||
if (count++ >= limit) break;
|
if (count++ >= limit)
|
||||||
|
break;
|
||||||
double percent = snapshot.total_b ? (s.bytes * 100.0 / snapshot.total_b) : 0.0;
|
double percent = snapshot.total_b ? (s.bytes * 100.0 / snapshot.total_b) : 0.0;
|
||||||
snapshot.pairs_rows.push_back({
|
snapshot.pairs_rows.push_back({
|
||||||
pair.first,
|
pair.first,
|
||||||
|
|
@ -202,16 +183,15 @@ void Stats::update_pairs(size_t limit) {
|
||||||
std::format("{:.2f}", percent),
|
std::format("{:.2f}", percent),
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Stats::update_packets(){
|
void Stats::update_packets() {
|
||||||
std::lock_guard lock(mtx);
|
std::lock_guard lock(mtx);
|
||||||
snapshot.packets_rows.clear();
|
snapshot.packets_rows.clear();
|
||||||
snapshot.packets_rows.push_back({ "IPVersion", "Transport protocol", "Source", "Destination", "App protocol"});
|
snapshot.packets_rows.push_back({"IPVersion", "Transport protocol", "Source", "Destination", "App protocol"});
|
||||||
|
|
||||||
for (auto& packet : packets) {
|
for (auto &packet : packets) {
|
||||||
snapshot.packets_rows.push_back({
|
snapshot.packets_rows.push_back({
|
||||||
packet.ip_version == IPVersion::v4 ? "IPv4" : "IPv6",
|
packet.ip_version == IPVersion::v4 ? "IPv4" : "IPv6",
|
||||||
transport_to_str(packet.transport_protocol),
|
transport_to_str(packet.transport_protocol),
|
||||||
|
|
@ -264,13 +244,11 @@ void Stats::update_bandwidth() {
|
||||||
last_b = snapshot.total_b;
|
last_b = snapshot.total_b;
|
||||||
last_tick = now;
|
last_tick = now;
|
||||||
const double alpha = 0.2;
|
const double alpha = 0.2;
|
||||||
smooth_bandwidth =
|
smooth_bandwidth = alpha * snapshot.bandwidth + (1.0 - alpha) * smooth_bandwidth;
|
||||||
alpha * snapshot.bandwidth + (1.0 - alpha) * smooth_bandwidth;
|
|
||||||
|
|
||||||
snapshot.bandwidth_history.push_back({ ts, smooth_bandwidth });
|
snapshot.bandwidth_history.push_back({ts, smooth_bandwidth});
|
||||||
}
|
}
|
||||||
snapshot.max_bandwidth = std::max(snapshot.max_bandwidth, snapshot.bandwidth);
|
snapshot.max_bandwidth = std::max(snapshot.max_bandwidth, snapshot.bandwidth);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -284,27 +262,23 @@ void Stats::update_bandwidth() {
|
||||||
* - Bandwidth history
|
* - Bandwidth history
|
||||||
*/
|
*/
|
||||||
|
|
||||||
void Stats::export_csv(const std::string& filename) {
|
void Stats::export_csv(const std::string &filename) {
|
||||||
std::lock_guard<std::mutex> lock(mtx);
|
std::lock_guard<std::mutex> lock(mtx);
|
||||||
std::ofstream file(filename);
|
std::ofstream file(filename);
|
||||||
if (!file.is_open()) return;
|
if (!file.is_open())
|
||||||
|
return;
|
||||||
|
|
||||||
file << "summary\n";
|
file << "summary\n";
|
||||||
file << "total_packets,total_bytes,bandwidth\n";
|
file << "total_packets,total_bytes,bandwidth\n";
|
||||||
file << snapshot.total_p << ","
|
file << snapshot.total_p << "," << snapshot.total_b << "," << snapshot.bandwidth << "\n\n";
|
||||||
<< snapshot.total_b << ","
|
|
||||||
<< snapshot.bandwidth << "\n\n";
|
|
||||||
|
|
||||||
// ===== Transport protocols =====
|
// ===== Transport protocols =====
|
||||||
file << "transport_protocols\n";
|
file << "transport_protocols\n";
|
||||||
file << "protocol,packets,bytes,percent\n";
|
file << "protocol,packets,bytes,percent\n";
|
||||||
|
|
||||||
for (const auto& [proto, s] : transport_map) {
|
for (const auto &[proto, s] : transport_map) {
|
||||||
double percent = snapshot.total_b ? (s.bytes * 100.0 / snapshot.total_b) : 0.0;
|
double percent = snapshot.total_b ? (s.bytes * 100.0 / snapshot.total_b) : 0.0;
|
||||||
file << transport_to_str(proto) << ","
|
file << transport_to_str(proto) << "," << s.packets << "," << s.bytes << "," << percent << "\n";
|
||||||
<< s.packets << ","
|
|
||||||
<< s.bytes << ","
|
|
||||||
<< percent << "\n";
|
|
||||||
}
|
}
|
||||||
file << "\n";
|
file << "\n";
|
||||||
|
|
||||||
|
|
@ -312,10 +286,8 @@ void Stats::export_csv(const std::string& filename) {
|
||||||
file << "application_protocols\n";
|
file << "application_protocols\n";
|
||||||
file << "protocol,packets,payload_bytes\n";
|
file << "protocol,packets,payload_bytes\n";
|
||||||
|
|
||||||
for (const auto& [proto, s] : application_map) {
|
for (const auto &[proto, s] : application_map) {
|
||||||
file << static_cast<int>(proto) << ","
|
file << static_cast<int>(proto) << "," << s.packets << "," << s.bytes << "\n";
|
||||||
<< s.packets << ","
|
|
||||||
<< s.bytes << "\n";
|
|
||||||
}
|
}
|
||||||
file << "\n";
|
file << "\n";
|
||||||
|
|
||||||
|
|
@ -323,24 +295,19 @@ void Stats::export_csv(const std::string& filename) {
|
||||||
file << "ip_stats\n";
|
file << "ip_stats\n";
|
||||||
file << "ip,packets_sent,packets_received,bytes_sent,bytes_received\n";
|
file << "ip,packets_sent,packets_received,bytes_sent,bytes_received\n";
|
||||||
|
|
||||||
for (const auto& [ip, s] : ip_map) {
|
for (const auto &[ip, s] : ip_map) {
|
||||||
file << ip << ","
|
file << ip << "," << s.packets_sent << "," << s.packets_received << "," << s.bytes_sent << ","
|
||||||
<< s.packets_sent << ","
|
|
||||||
<< s.packets_received << ","
|
|
||||||
<< s.bytes_sent << ","
|
|
||||||
<< s.bytes_received << "\n";
|
<< s.bytes_received << "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// bandwidth
|
||||||
//bandwidth
|
|
||||||
file << "time,bandwidth\n";
|
file << "time,bandwidth\n";
|
||||||
|
|
||||||
for (const auto& p : snapshot.bandwidth_history) {
|
for (const auto &p : snapshot.bandwidth_history) {
|
||||||
file << p.timestamp << "," << p.bytes_per_sec << "\n";
|
file << p.timestamp << "," << p.bytes_per_sec << "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
file.close();
|
file.close();
|
||||||
|
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* @brief Exports statistics to JSON format.
|
* @brief Exports statistics to JSON format.
|
||||||
|
|
@ -350,10 +317,11 @@ void Stats::export_csv(const std::string& filename) {
|
||||||
* - Visualization tools
|
* - Visualization tools
|
||||||
* - Data pipelines
|
* - Data pipelines
|
||||||
*/
|
*/
|
||||||
void Stats::export_json(const std::string& filename) {
|
void Stats::export_json(const std::string &filename) {
|
||||||
std::lock_guard<std::mutex> lock(mtx);
|
std::lock_guard<std::mutex> lock(mtx);
|
||||||
std::ofstream file(filename);
|
std::ofstream file(filename);
|
||||||
if (!file.is_open()) return;
|
if (!file.is_open())
|
||||||
|
return;
|
||||||
|
|
||||||
file << "{\n";
|
file << "{\n";
|
||||||
|
|
||||||
|
|
@ -367,8 +335,9 @@ void Stats::export_json(const std::string& filename) {
|
||||||
// ===== Transport =====
|
// ===== Transport =====
|
||||||
file << " \"transport\": [\n";
|
file << " \"transport\": [\n";
|
||||||
bool first = true;
|
bool first = true;
|
||||||
for (const auto& [proto, s] : transport_map) {
|
for (const auto &[proto, s] : transport_map) {
|
||||||
if (!first) file << ",\n";
|
if (!first)
|
||||||
|
file << ",\n";
|
||||||
first = false;
|
first = false;
|
||||||
|
|
||||||
double percent = snapshot.total_b ? (s.bytes * 100.0 / snapshot.total_b) : 0.0;
|
double percent = snapshot.total_b ? (s.bytes * 100.0 / snapshot.total_b) : 0.0;
|
||||||
|
|
@ -385,8 +354,9 @@ void Stats::export_json(const std::string& filename) {
|
||||||
// ===== IP stats =====
|
// ===== IP stats =====
|
||||||
file << " \"top_ips\": [\n";
|
file << " \"top_ips\": [\n";
|
||||||
first = true;
|
first = true;
|
||||||
for (const auto& [ip, s] : ip_map) {
|
for (const auto &[ip, s] : ip_map) {
|
||||||
if (!first) file << ",\n";
|
if (!first)
|
||||||
|
file << ",\n";
|
||||||
first = false;
|
first = false;
|
||||||
|
|
||||||
file << " {\n";
|
file << " {\n";
|
||||||
|
|
@ -402,8 +372,9 @@ void Stats::export_json(const std::string& filename) {
|
||||||
file << ",\n \"communication_pairs\": [\n";
|
file << ",\n \"communication_pairs\": [\n";
|
||||||
first = true;
|
first = true;
|
||||||
|
|
||||||
for (const auto& [pair, s] : pairs) {
|
for (const auto &[pair, s] : pairs) {
|
||||||
if (!first) file << ",\n";
|
if (!first)
|
||||||
|
file << ",\n";
|
||||||
first = false;
|
first = false;
|
||||||
|
|
||||||
file << " {\n";
|
file << " {\n";
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue