diff --git a/PROJECTS/beginner/network-traffic-analyzer/cpp/CMakeLists.txt b/PROJECTS/beginner/network-traffic-analyzer/cpp/CMakeLists.txt new file mode 100644 index 00000000..f9070c3d --- /dev/null +++ b/PROJECTS/beginner/network-traffic-analyzer/cpp/CMakeLists.txt @@ -0,0 +1,48 @@ +cmake_minimum_required(VERSION 3.31) +project(network-traffic-analyzer) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +link_libraries(pcap) + +find_package(Boost REQUIRED COMPONENTS program_options) +add_executable(network-traffic-analyzer main.cpp + "include/capture/pcapCapture.hpp" + src/capture/pcapCapture.cpp + "include/cli/argsParse.hpp" + include/packet/packet.hpp + include/packet/IP.hpp + src/packet/IP.cpp + include/stats/protocolStats.hpp + src/stats/protocolStats.cpp + src/packet/packet.cpp + src/cli/argsParse.cpp + include/cli/filter.hpp + src/cli/filter.cpp + include/TUI/view.hpp + src/TUI/view.cpp +) + +include(FetchContent) + +set(FETCHCONTENT_UPDATES_DISCONNECTED ON) +set(FETCHCONTENT_QUIET OFF) + +FetchContent_Declare(ftxui + GIT_REPOSITORY https://github.com/arthursonzogni/ftxui.git + GIT_TAG v5.0.0 + GIT_PROGRESS TRUE + GIT_SHALLOW TRUE + EXCLUDE_FROM_ALL +) +FetchContent_MakeAvailable(ftxui) + + +target_link_libraries(network-traffic-analyzer + Boost::program_options + ftxui::screen + ftxui::dom + ftxui::component +) diff --git a/PROJECTS/beginner/network-traffic-analyzer/cpp/README.md b/PROJECTS/beginner/network-traffic-analyzer/cpp/README.md new file mode 100644 index 00000000..b0bd911f --- /dev/null +++ b/PROJECTS/beginner/network-traffic-analyzer/cpp/README.md @@ -0,0 +1,87 @@ +```ruby +▄▄▄▄▄▄ ▄▄▄▄ ▄▄▄ ▄▄▄▄▄ ▄▄▄▄▄ ▄▄ ▄▄▄▄ ▄▄▄ ▄▄ ▄▄ ▄▄▄ ▄▄ ▄▄ ▄▄ ▄▄▄▄▄ ▄▄▄▄▄ ▄▄▄▄ + ██ ██▄█▄ ██▀██ ██▄▄ ██▄▄ ██ ██▀▀▀ ██▀██ ███▄██ ██▀██ ██ ▀███▀ ▄█▀ ██▄▄ ██▄█▄ + ██ ██ ██ ██▀██ ██ ██ ██ ▀████ ██▀██ ██ ▀██ ██▀██ ██▄▄▄ █ ▄██▄▄ ██▄▄▄ ██ ██ + +``` + +>A high-performance CLI network analyzer built with libpcap for raw packet capture and FTXUI for a fully interactive terminal UI. +The application captures packets directly from a network interface, parses protocol headers manually, aggregates statistics in real time + +--- +![Preview](example.png) + +> [!IMPORTANT] +> Packet capture requires elevated privileges. + +Run with: + +```bash +sudo ./network-traffic-analyzer +``` + +Or grant capabilities: + +```bash +sudo setcap cap_net_raw,cap_net_admin=eip ./network-traffic-analyzer +``` + +--- + +# Features +1) ## Live Packet Capture +- Capture traffic from a selected network interface +- Support for BPF filters (e.g. tcp, port 80, udp) +- Real-time processing using libpcap + +2) ## Real-Time Statistics Engine +- Total packets & traffic volume +- Transport protocol distribution (TCP / UDP / ICMP) +- Application-level classification (port-based) +- Top IP addresses +- Top source > destination pairs + +3) ## Flexible Capture Modes +- Live capture from selected network interface (-i, --interface) +- Offline analysis from .pcap file (-r, --offline) +- Packet count limit (-c) +- Time limit for capture (-t) +- Interface discovery (--interfaces) + +> [!TIP] +> For the complete list of CLI options, use: +> `--help` + +# Technologies +- C++20+ +- Boost::program_options +- libpcap +- FTXUI +- CMake + +# Build +``` +mkdir build && cd build +cmake .. +make +``` + +# Usage Example + +### Live capture on eth0 +``` +sudo ./network-traffic-analyzer -i eth0 +``` +### Capture 100 packets +``` +sudo ./network-traffic-analyzer -i wlan0 -c 100 +``` +### Analyze offline pcap file +``` +sudo ./network-traffic-analyzer --offline traffic.pcap +``` +### Export results (json / csv) +``` +sudo ./network-traffic-analyzer --json result.json --csv result.csv + +``` diff --git a/PROJECTS/beginner/network-traffic-analyzer/cpp/example.png b/PROJECTS/beginner/network-traffic-analyzer/cpp/example.png new file mode 100644 index 00000000..0cc88ba0 Binary files /dev/null and b/PROJECTS/beginner/network-traffic-analyzer/cpp/example.png differ diff --git a/PROJECTS/beginner/network-traffic-analyzer/cpp/include/TUI/view.hpp b/PROJECTS/beginner/network-traffic-analyzer/cpp/include/TUI/view.hpp new file mode 100644 index 00000000..29e78883 --- /dev/null +++ b/PROJECTS/beginner/network-traffic-analyzer/cpp/include/TUI/view.hpp @@ -0,0 +1,40 @@ +#ifndef VIEW_HPP +#define VIEW_HPP +#include "../stats/protocolStats.hpp" +#include + +class View { +public: + ftxui::Element render( + const StatsSnapshot& data, + const std::string& interface, + const std::string& filter, + bool capture_finished, + std::chrono::seconds timer + ); + +private: + ftxui::Element render_header( + const StatsSnapshot& data, + const std::string& interface, + const std::string& filter + ); + ftxui::Element render_stats(const StatsSnapshot& data); + + ftxui::Element render_transport(const StatsSnapshot& data); + ftxui::Element render_application(const StatsSnapshot& data); + ftxui::Element render_ip(const StatsSnapshot& data); + ftxui::Element render_pairs(const StatsSnapshot& data); + ftxui::Element render_bandwidth(const StatsSnapshot& data); + ftxui::Element render_packets(const StatsSnapshot& data); + + ftxui::Element render_footer( + bool capture_finished, + std::chrono::seconds timer + ); +}; + + + + +#endif //VIEW_HPP diff --git a/PROJECTS/beginner/network-traffic-analyzer/cpp/include/capture/pcapCapture.hpp b/PROJECTS/beginner/network-traffic-analyzer/cpp/include/capture/pcapCapture.hpp new file mode 100644 index 00000000..1320b558 --- /dev/null +++ b/PROJECTS/beginner/network-traffic-analyzer/cpp/include/capture/pcapCapture.hpp @@ -0,0 +1,107 @@ +#ifndef PCAPCAPTURE_HPP +#define PCAPCAPTURE_HPP + +#include +#include +#include +#include +#include + +//include C libraries +extern "C" { +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define SNAP_LEN 1518 +} + +#include "../packet/IP.hpp" +#include "../../include/stats/protocolStats.hpp" +#include "../packet/packet.hpp" + +/** + * Packet capture engine based on libpcap + * + * Supports: + * - Live capture from network interface + * - Offline capture from .pcap file + * - BPF filtering + * - Separate capture thread (for live mode) + * + * Workflow: + * initialize() -> load interfaces + * set_capabilities()-> configure capture parameters + * start() -> start live capture (threaded) + * start_offline() -> process file synchronously + * stop() -> stop capture and cleanup + */ + +class PcapCapture { +private: + /* libpcap error buffer */ + char errbuf[PCAP_ERRBUF_SIZE]; + + /* filter expression (compiled before capture) */ + std::string filter_exp = ""; + /* compiled filter program (expression) */ + struct bpf_program fp = {}; + /* Active pcap handle */ + pcap_t *handle = nullptr; + + /* Network mask and IP */ + bpf_u_int32 mask = 0; + bpf_u_int32 net = 0; + + /* Number of packets to capture */ + int num_packets = 0; + + /* Linked list of available interfaces */ + pcap_if_t *interfaces = nullptr; + + /* Selected network interface */ + std::string interface; + /** + * Static wrapper required by C-style libpcap callback. + * + * Since libpcap expects a C function pointer, + * we use a static function and forward the call + * to the class instance. + */ + static void callback(u_char *args, const struct pcap_pkthdr *header, const u_char *packet); + // packet processing logic + void got_packet(const struct pcap_pkthdr *header, const u_char *packet); + + /* Separate thread used for live capture */ + std::thread thread; + std::atomic running{false}; + +public: + + void print_interfaces(); + + bool isRunning() { + return running; + } + void setRunning(bool running) { + this->running = running; + } + /* Pointer to statistics engine */ + Stats* stats; + + void set_capabilities(std::string& interface, int num_packets, std::string& filter_exp, int packets_limit, Stats* stats); + void initialize(); + + + void start(); + void start_offline(std::string fpath); + void stop(); +}; + + +#endif //PCAPCAPTURE_HPP diff --git a/PROJECTS/beginner/network-traffic-analyzer/cpp/include/cli/argsParse.hpp b/PROJECTS/beginner/network-traffic-analyzer/cpp/include/cli/argsParse.hpp new file mode 100644 index 00000000..9795e854 --- /dev/null +++ b/PROJECTS/beginner/network-traffic-analyzer/cpp/include/cli/argsParse.hpp @@ -0,0 +1,14 @@ +#ifndef ARGSPARSE_HPP +#define ARGSPARSE_HPP +#include + +namespace po = boost::program_options; +struct argsParser { + po::options_description desc; + po::variables_map vm; + void print_help() const; + + argsParser(int argc, char** argv); +}; + +#endif //ARGSPARSE_HPP diff --git a/PROJECTS/beginner/network-traffic-analyzer/cpp/include/cli/filter.hpp b/PROJECTS/beginner/network-traffic-analyzer/cpp/include/cli/filter.hpp new file mode 100644 index 00000000..95c66e24 --- /dev/null +++ b/PROJECTS/beginner/network-traffic-analyzer/cpp/include/cli/filter.hpp @@ -0,0 +1,26 @@ +#ifndef FILTER_HPP +#define FILTER_HPP +#include +#include + +enum filter_type { + PROTOCOL, + PORT, + IP_TYPE, + IP_SRC, + IP_DEST, + NONE +}; + +struct filter { + filter_type type; + std::string val; +}; + + +filter parse(std::string str); +std::string get_bpf_filter(std::vector& f); + + + +#endif //FILTER_HPP diff --git a/PROJECTS/beginner/network-traffic-analyzer/cpp/include/packet/IP.hpp b/PROJECTS/beginner/network-traffic-analyzer/cpp/include/packet/IP.hpp new file mode 100644 index 00000000..c1a4860a --- /dev/null +++ b/PROJECTS/beginner/network-traffic-analyzer/cpp/include/packet/IP.hpp @@ -0,0 +1,91 @@ +#ifndef IP_HPP +#define IP_HPP +#include +#include +#include +#include +#include "packet.hpp" + +/* virtual class for our IPv4, IPv6 classes */ +class IP_class { +protected: + virtual void handle_tcp() = 0; + virtual void handle_udp() = 0; + virtual void handle_icmp() = 0; + virtual void handle_icmpv6() = 0; + virtual void handle_igmp() = 0; + uint16_t payload_len = 0; +public: + //getters + virtual std::string get_source() = 0; + virtual std::string get_dest() = 0; + virtual uint16_t get_src_port() = 0; + virtual uint16_t get_dest_port() = 0; + virtual TransportProtocol get_protocol() = 0; + + uint16_t get_payload_len(); + + const uint8_t* payload_ptr = nullptr; + const uint8_t* get_payload_ptr() const { return payload_ptr; } + + virtual ~IP_class() = default; + +}; +/*** ip4 ***/ +class IPv4 : public IP_class { +private: + const ip* ip_hdr = nullptr; + int ip_hdr_len = 0; + uint16_t src_port = 0; + uint16_t dest_port = 0; +protected: + void handle_tcp() override; + void handle_udp() override; + void handle_icmp() override; + void handle_icmpv6() override; + void handle_igmp() override; + +public: + TransportProtocol get_protocol() override; + std::string get_source() override; + std::string get_dest() override; + uint16_t get_src_port() override; + uint16_t get_dest_port() override; + + explicit IPv4(const u_char* data); +}; + + + +/*** ipv6 ***/ +class IPv6 : public IP_class{ +protected: + void handle_tcp() override; + void handle_udp() override; + void handle_icmp() override; + void handle_icmpv6() override; + void handle_igmp() override; +private: + const ip6_hdr* ip_hdr = nullptr; + int ip_hdr_len = 40; + + in6_addr ip_source; + in6_addr ip_dest; + + uint16_t src_port; + uint16_t dest_port; + + const uint8_t* ptr = nullptr; + +public: + explicit IPv6(const u_char* data); + + TransportProtocol get_protocol() override; + std::string get_source() override; + std::string get_dest() override; + uint16_t get_src_port() override; + uint16_t get_dest_port() override; + +}; + +#endif //IP_HPP diff --git a/PROJECTS/beginner/network-traffic-analyzer/cpp/include/packet/packet.hpp b/PROJECTS/beginner/network-traffic-analyzer/cpp/include/packet/packet.hpp new file mode 100644 index 00000000..39e55bba --- /dev/null +++ b/PROJECTS/beginner/network-traffic-analyzer/cpp/include/packet/packet.hpp @@ -0,0 +1,59 @@ +#ifndef PACKET_HPP +#define PACKET_HPP +#include +#include +#include +enum IPVersion { + v4, v6, +}; + +enum class TransportProtocol { + TCP = 1, + UDP = 2, + ICMP = 3, + ICMP6 = 4, + IGMP = 5, + UNKNOWN = -1, +}; + +enum class ApplicationProtocol { + HTTP, + HTTPS, + DNS, + FTP, + SSH, + SMTP, + QUIC, + NTP, + UNKNOWN, +}; + +struct Packet { + IPVersion ip_version; + TransportProtocol transport_protocol; + ApplicationProtocol application_protocol; + //src address + std::string src; + //dest address + std::string dst; + uint16_t src_port; + uint16_t dst_port; + + uint32_t total_len; + uint16_t payload_len; + + const uint8_t* payload_ptr; + + Packet(IPVersion version, TransportProtocol protocol, std::string src, std::string dst, + uint16_t src_port, uint16_t dst_port, uint32_t total_len, uint16_t payload, const uint8_t* payload_ptr) : ip_version(version), transport_protocol(protocol), + src(std::move(src)), dst(std::move(dst)), src_port(src_port), dst_port(dst_port), total_len(total_len), payload_len(payload), + payload_ptr(payload_ptr) + { + this->application_protocol = get_application_protocol(); + } + Packet() { } + +private: + ApplicationProtocol get_application_protocol(); +}; +#endif //PACKET_HPP diff --git a/PROJECTS/beginner/network-traffic-analyzer/cpp/include/stats/protocolStats.hpp b/PROJECTS/beginner/network-traffic-analyzer/cpp/include/stats/protocolStats.hpp new file mode 100644 index 00000000..13e77383 --- /dev/null +++ b/PROJECTS/beginner/network-traffic-analyzer/cpp/include/stats/protocolStats.hpp @@ -0,0 +1,120 @@ +#ifndef PROTOCOLSTATS_HPP +#define PROTOCOLSTATS_HPP + +#include +#include +#include "../packet/packet.hpp" +#include +#include +#include +#include +#include +#include "ftxui/dom/elements.hpp" + +using namespace ftxui; +struct protocolStats { + uint32_t packets = 0; + uint32_t bytes = 0; +}; + +struct trafficStats { + uint32_t total_packets = 0; + uint32_t total_bytes = 0; +}; + +struct IPStats { + uint32_t bytes_sent = 0; + uint32_t bytes_received = 0; + + uint32_t packets_sent = 0; + uint32_t packets_received = 0; +}; + +struct BandwidthPoint { + double timestamp; + double bytes_per_sec; +}; + +struct StatsSnapshot { + std::vector> transport_rows; + std::vector> app_rows; + std::vector> rows; + std::vector> pairs_rows; + std::vector> packets_rows; + + uint32_t total_p = 0, total_b = 0; + //bandwidth + std::vector bandwidth_history; + double bandwidth = 0; + mutable double max_bandwidth = 0; + +}; + + +/** + * @brief Thread-safe statistics engine. + * + * Responsible for: + * - Aggregating packet statistics + * - Maintaining protocol distributions + * - Tracking IP traffic + * - Calculating bandwidth + * - Providing snapshot for UI rendering + * + * All write operations are protected by mutex. + */ +class Stats { +private: + std::mutex mtx; + + uint32_t last_b = 0; + + std::chrono::steady_clock::time_point last_tick; + + + std::unordered_map transport_map; + std::unordered_map application_map; + std::unordered_map ip_map; + + std::map, protocolStats> pairs; + + std::deque packets; + int limit_packets = 10; + + +public: + + void push(const Packet& p) { + std::lock_guard lock(mtx); + if (packets.size() > static_cast(limit_packets)) { + packets.pop_front(); + } + packets.push_back(p); + } + + StatsSnapshot snapshot; + void update_bandwidth(); + double smooth_value(size_t i, size_t start); + double smooth_bandwidth = 0.0; + + void set_packets_limit(int limit) { + limit_packets = limit; + } + + void add_packet(Packet &packet); + + void update_transport_stats(); + void update_application_stats(); + void update_ip_stats(size_t limit); + void update_pairs(size_t limit = 10); + void update_packets(); + + void export_csv(const std::string& filename); + void export_json(const std::string& filename); + + Stats(); +}; + + + +#endif //PROTOCOLSTATS_HPP diff --git a/PROJECTS/beginner/network-traffic-analyzer/cpp/main.cpp b/PROJECTS/beginner/network-traffic-analyzer/cpp/main.cpp new file mode 100644 index 00000000..7aa3910f --- /dev/null +++ b/PROJECTS/beginner/network-traffic-analyzer/cpp/main.cpp @@ -0,0 +1,153 @@ +#include "include/capture/pcapCapture.hpp" +#include +#include +#include +#include "include/cli/filter.hpp" +#include +#include +#include + + +#include "include/cli/argsParse.hpp" +#include "include/TUI/view.hpp" +#define SNAP_LEN 1518 + +int main(int argc, char **argv) +{ + /* initialize capture */ + PcapCapture capture; + capture.initialize(); + + /* initialize stats */ + Stats stats; + + /* initializing the command line parser */ + argsParser parser(argc, argv); + + /* check helper flags */ + if (parser.vm.contains("help")) { + parser.print_help(); + return 0; + } + if (parser.vm.contains("interfaces")) { + capture.print_interfaces(); + return 0; + } + /* take the arguments of the flags into variables */ + std::string interface = parser.vm["interface"].as(); + int count = parser.vm["count"].as(); + int limit = parser.vm["limit"].as(); + int time = parser.vm["time"].as(); + std::string filterString = ""; + + /* get a filter, use vector for multiple */ + std::vector filters; + if (parser.vm.contains("filter")) { + auto& f = parser.vm["filter"].as>(); + for (auto& x : f) { + filters.push_back(parse(x)); + filterString += x + " "; + } + } + /* converting the filter to a pcap readable string */ + std::string expression = get_bpf_filter(filters); + + bool isOffline = parser.vm.contains("offline"); + + /* set the flags to capture engine */ + capture.set_capabilities(interface, count, expression, limit, &stats); + + + std::atomic capture_finished = false; + + /* if we capture packets offline, we read the file in full, then print the result */ + if (isOffline) { + + capture.start_offline(parser.vm["offline"].as()); + + /* full recalculation of statistics after file processing */ + stats.update_packets(); + stats.update_application_stats(); + stats.update_transport_stats(); + stats.update_ip_stats(10); + stats.update_pairs(); + stats.update_bandwidth(); + } + /* otherwise start live capture */ + else { + capture.start(); + } + + /* UI */ + using namespace ftxui; + auto screen = ScreenInteractive::Fullscreen(); + /* our class for UI */ + View view; + + /* timer */ + std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now(); + std::chrono::seconds timer{0}; + + auto renderer = Renderer([&] { + auto snapshot = stats.snapshot; + return view.render(snapshot, + interface, + filterString, + capture_finished, + timer); + }); + + auto component = CatchEvent(renderer, [&](Event e) { + if (e == Event::Character('q') || e == Event::Escape) { + capture.stop(); + screen.Exit(); + } + return true; + }); + + + std::thread updater; + + if (!isOffline) { + updater = std::thread([&] { + while (capture.isRunning()) { + + stats.update_packets(); + stats.update_application_stats(); + stats.update_transport_stats(); + stats.update_ip_stats(10); + stats.update_pairs(); + stats.update_bandwidth(); + + screen.PostEvent(Event::Custom); + std::this_thread::sleep_for(std::chrono::milliseconds(300)); + + auto current_time = std::chrono::steady_clock::now(); + timer = std::chrono::duration_cast( + current_time - begin); + + if (timer >= std::chrono::seconds(time)) + break; + } + + capture_finished = true; + screen.PostEvent(Event::Custom); + }); + } + + screen.Loop(component); + + if (updater.joinable()) + updater.join(); + + /* check export flags */ + if (parser.vm.contains("csv")) { + stats.export_csv(parser.vm["csv"].as()); + } + if (parser.vm.contains("json")) { + stats.export_json(parser.vm["json"].as()); + } + + return 0; +} + diff --git a/PROJECTS/beginner/network-traffic-analyzer/cpp/src/TUI/view.cpp b/PROJECTS/beginner/network-traffic-analyzer/cpp/src/TUI/view.cpp new file mode 100644 index 00000000..588734d9 --- /dev/null +++ b/PROJECTS/beginner/network-traffic-analyzer/cpp/src/TUI/view.cpp @@ -0,0 +1,229 @@ +#include "../../include/TUI/view.hpp" +#include "ftxui/dom/table.hpp" + + +ftxui::Element View::render(const StatsSnapshot& data, const std::string& interface, const std::string& filter, + bool capture_finished, std::chrono::seconds timer) { + auto header = render_header(data, interface, filter); + + auto transport_section = + hbox({ + render_transport(data) | flex, + separator(), + render_application(data) | flex, + separator(), + render_pairs(data) | flex, + }) | border; + + auto ip_section = + hbox({ + render_ip(data) + | border + | size(HEIGHT, LESS_THAN, 10) + | frame + | vscroll_indicator, + + render_bandwidth(data) + | border + | flex + }); + + auto left_panel = + vbox({ + transport_section, + separator(), + ip_section, + }) | flex_grow; + + auto right_panel = + render_packets(data) + | border + | size(WIDTH, EQUAL, 100) + | frame + | vscroll_indicator; + + auto body = + hbox({ + left_panel, + separator(), + right_panel, + }) | flex; + + auto footer = render_footer(capture_finished, timer); + + return vbox({ + header, + separator(), + body, + separator(), + footer, + }); +} +/** + * @brief Renders top header section. + * + * Displays: + * - Application title + * - Active interface + * - Active filter + * - Traffic summary + */ +ftxui::Element View::render_header(const StatsSnapshot& data, const std::string& interface, const std::string& filter) { + return hbox({ + vbox({ + text("Network Traffic Analyzer") | bold, + text("Interface: " + interface), + text("Filter: " + filter), + }) | flex, + separator(), + render_stats(data) | flex, + }) | border; +} + +ftxui::Element View::render_stats(const StatsSnapshot& data) { + return vbox({ + text("=== Traffic summary ===") | bold, + text("Total packets: " + std::to_string(data.total_p)), + text(std::format( + "Total bytes : {:.2f} MB", + data.total_b / (1024.0 * 1024.0) + )) + }) | flex; +} + + +ftxui::Element View::render_footer(bool capture_finished, std::chrono::seconds timer) { + return Element({ + capture_finished + ? text("Capture finished (" + std::format("{}", timer) + "). Press 'q' or Esc to exit.") + | bold | color(Color::Yellow) | center + : text("time: " + std::format("{}", timer) + ". Press 'q' or Esc to exit.") | center + | size(HEIGHT, EQUAL, 1) + }); + +} +ftxui::Element View::render_transport(const StatsSnapshot& data) { + Table table(data.transport_rows); + table.SelectAll().Border(LIGHT); + + table.SelectRow(0).Decorate(bold); + table.SelectRow(0).SeparatorVertical(LIGHT); + table.SelectRow(0).Decorate(bold); + table.SelectRow(0).Border(DOUBLE); + + return vbox({ + text("=== Transport protocols === ") | bold, + table.Render() + }) | flex; +} +ftxui::Element View::render_application(const StatsSnapshot& data) { + Table table(data.app_rows); + table.SelectAll().Border(LIGHT); + + table.SelectRow(0).Decorate(bold); + table.SelectRow(0).SeparatorVertical(LIGHT); + table.SelectRow(0).Decorate(bold); + table.SelectRow(0).Border(DOUBLE); + + return vbox({ + text("=== Application protocols ===") | bold, + table.Render() + }) | flex; +} +ftxui::Element View::render_ip(const StatsSnapshot& data) { + + Table table(data.rows); + table.SelectAll().Border(LIGHT); + + table.SelectRow(0).Decorate(bold); + table.SelectRow(0).SeparatorVertical(LIGHT); + table.SelectRow(0).Decorate(bold); + table.SelectRow(0).Border(DOUBLE); + + return vbox({ + text("=== Top IP addresses ===") | bold, + + table.Render() + }) | flex; +} +ftxui::Element View::render_pairs(const StatsSnapshot& data) { + Table table(data.pairs_rows); + table.SelectAll().Border(LIGHT); + + table.SelectRow(0).Decorate(bold); + table.SelectRow(0).SeparatorVertical(LIGHT); + table.SelectRow(0).Decorate(bold); + table.SelectRow(0).Border(DOUBLE); + + return vbox({ + text("=== Top communication pairs ===") | bold, + table.Render() + }) | flex; +} +/** + * @brief Renders bandwidth graph. + * + * Displays last 50 samples. + * Scales dynamically based on max bandwidth. + */ + +ftxui::Element View::render_bandwidth(const StatsSnapshot& data) { + using namespace ftxui; + + GraphFunction fn = [this, data](int width, int height) { + std::vector output(width, 0); + + if (data.bandwidth_history.size() < 2) + return output; + + size_t n = data.bandwidth_history.size(); + size_t start = n > 50 ? n - 50 : 0; + + + double max_bw = 1.0; + for (size_t i = start; i < n; ++i) + max_bw = std::max(max_bw, data.bandwidth_history[i].bytes_per_sec); + + for (int x = 0; x < width; ++x) { + + double t = (double)x / (width - 1); + + + double idx_f = start + t * (n - start - 1); + size_t i0 = (size_t)idx_f; + size_t i1 = std::min(i0 + 1, n - 1); + + + double frac = idx_f - i0; + double bw = + data.bandwidth_history[i0].bytes_per_sec * (1.0 - frac) + + data.bandwidth_history[i1].bytes_per_sec * frac; + + double v = bw / max_bw; + output[x] = static_cast(v * (height - 1)); + } + + return output; + }; + return vbox({ + text(std::format(" Bandwidth: {:.2f} KB / max: {:.2f} KB", data.bandwidth, data.max_bandwidth)) | bold, + graph(fn) + | size(HEIGHT, EQUAL, 20) | size(WIDTH, EQUAL, 60) + | border + | color(Color::Green), +}) ; +} +ftxui::Element View::render_packets(const StatsSnapshot& data) { + Table table(data.packets_rows); + table.SelectAll().Border(LIGHT); + + table.SelectRow(0).Decorate(bold); + table.SelectRow(0).SeparatorVertical(LIGHT); + table.SelectRow(0).Decorate(bold); + table.SelectRow(0).Border(DOUBLE); + return vbox({ + text("=== Packets ===") | bold, + + table.Render() | flex + }) | flex; +} \ No newline at end of file diff --git a/PROJECTS/beginner/network-traffic-analyzer/cpp/src/capture/pcapCapture.cpp b/PROJECTS/beginner/network-traffic-analyzer/cpp/src/capture/pcapCapture.cpp new file mode 100644 index 00000000..c5ed7334 --- /dev/null +++ b/PROJECTS/beginner/network-traffic-analyzer/cpp/src/capture/pcapCapture.cpp @@ -0,0 +1,206 @@ +#include "../../include/capture/pcapCapture.hpp" +#include "../../include/stats/protocolStats.hpp" + + +/* get a list of all available network interfaces */ +void PcapCapture::initialize() { + /* find all devs available in network, save them to pcap_if_t struct (interfaces) */ + if (pcap_findalldevs(&interfaces, errbuf) == -1) { + fprintf(stderr, "Error: pcap_findalldevs has been failed - %s\n", errbuf); + } +} +/** + * Start live packet capture. + * + * Steps: + * 1. Resolve network mask + * 2. Open device in promiscuous mode + * 3. Compile and apply BPF filter (if provided) + * 4. Start pcap_loop in a separate thread + */ +void PcapCapture::start() { + // getting the netmask of the interface + if (pcap_lookupnet(interface.c_str(), &net, &mask, errbuf) == -1) { + fprintf(stderr, "Couldn't get netmask for device %s: %s\n", + interface.c_str(), errbuf); + net = 0; + mask = 0; + } + + /* open capture device */ + handle = pcap_open_live(interface.c_str(), SNAP_LEN, 1, 1000, errbuf); + if (handle == nullptr) { + fprintf(stderr, "Couldn't open device %s: %s\n", interface.c_str(), errbuf); + exit(EXIT_FAILURE); + } + + if (pcap_datalink(handle) != DLT_EN10MB) { + fprintf(stderr, "%s is not an Ethernet\n", interface.c_str()); + exit(EXIT_FAILURE); + } + if (!filter_exp.empty()) { + /* compile the filter expression */ + if (pcap_compile(handle, &fp, filter_exp.c_str(), 0, net) == -1) { + fprintf(stderr, "Couldn't parse filter %s: %s\n", + filter_exp.c_str(), pcap_geterr(handle)); + exit(EXIT_FAILURE); + } + + + /* apply the compiled filter */ + if (pcap_setfilter(handle, &fp) == -1) { + fprintf(stderr, "Couldn't install filter %s: %s\n", + filter_exp.c_str(), pcap_geterr(handle)); + exit(EXIT_FAILURE); + } + pcap_freecode(&fp); + } + + + + /* start a separate thread */ + running = true; + thread = std::thread([this]() { + if (pcap_loop(handle, num_packets, &PcapCapture::callback, reinterpret_cast(this)) < 0) { + //fprintf(stderr, "Error in pcap_loop: %s\n", pcap_geterr(handle)); + //pcap_close(handle); + } + running = false; + }); + +} + +void PcapCapture::stop() { + if (interfaces) { + pcap_freealldevs(interfaces); + } + if (handle) { + if (running == true) pcap_breakloop(handle); + pcap_close(handle); + //handle = nullptr; + } + + if (thread.joinable()) { + thread.join(); + } + /*if (!filter_exp.empty()) { + pcap_freecode(&fp); + }*/ + +} +/* print all available interfaces */ +void PcapCapture::print_interfaces() { + int i = 0; + for (pcap_if_t *dev = interfaces; dev; dev = dev->next) { + printf("%d. %s ",++i, dev->name); + if (dev->description) { + printf("(%s)\n", dev->description); + } + else { + printf("\n"); + } + } +} +/** + * @brief Static wrapper required by libpcap C API. + * + * Since libpcap expects a C-style function pointer, + * we forward the call to the current class instance. + * + * @param user Pointer to PcapCapture instance + * @param header Packet metadata + * @param packet Raw packet bytes + */ +void PcapCapture::callback( + u_char* user, + const struct pcap_pkthdr* header, + const u_char* packet +) { + auto* self = reinterpret_cast(user); + if (!self->isRunning()) return; + self->got_packet(header, packet); +} + +/** + * @brief Parses a single captured packet. + * + * Responsibilities: + * - Parse Ethernet header + * - Detect IP version (IPv4 / IPv6) + * - Extract transport & application protocols + * - Construct Packet abstraction + * - Forward packet to Stats engine + * + * Only IPv4 and IPv6 are currently processed. + * Other Ethernet types are ignored. + */ +void PcapCapture::got_packet(const struct pcap_pkthdr *header, const u_char *packet) { + if (!running) return; + + // --- Ethernet header --- + const auto* ethernet = reinterpret_cast(packet); + uint16_t ether_type = ntohs(ethernet->ether_type); + + Packet packetView; + + /* if we have a ipv4 type */ + if (ether_type == ETHERTYPE_IP) { + IPv4 ip(packet + sizeof(struct ether_header)); + TransportProtocol prot = ip.get_protocol(); + + packetView = Packet(v4, prot, ip.get_source(), ip.get_dest(), ip.get_src_port(), ip.get_dest_port(), header->len, ip.get_payload_len(), ip.get_payload_ptr()); + stats->add_packet(packetView); + stats->push(packetView); + + } + /* ipv6 type */ + if (ether_type == ETHERTYPE_IPV6) { + IPv6 ip(packet + sizeof(struct ether_header)); + TransportProtocol prot = ip.get_protocol(); + packetView = Packet(v6, prot, ip.get_source(), ip.get_dest(), ip.get_src_port(), ip.get_dest_port(), header->len, ip.get_payload_len(), ip.get_payload_ptr()); + stats->add_packet(packetView); + stats->push(packetView); + + } + if (ether_type == ETHERTYPE_VLAN) { + ethernet = (ether_header*)(packet + 4); + + } + if (ether_type == ETHERTYPE_ARP) { + + } +} + +void PcapCapture::set_capabilities(std::string& interface, int num_packets, std::string& filter_exp, int packets_limit, Stats* stats) { + this->interface = interface; + this->num_packets = num_packets; + this->filter_exp = filter_exp; + this->stats = stats; + this->stats->set_packets_limit(packets_limit); + +} +/** + * @brief Processes packets from an offline .pcap file. + * + * Differences from live mode: + * - Runs synchronously + * - No additional thread is created + * - Blocks until entire file is processed + * + * Used for post-capture analysis and exporting results. + */ +void PcapCapture::start_offline(std::string fpath) { + handle = pcap_open_offline(fpath.c_str(), errbuf); + if (handle == nullptr) { + fprintf(stderr, "Error opening offline file: %s\n", errbuf); + return; + } + + running = true; + + pcap_loop(handle, num_packets, + &PcapCapture::callback, + reinterpret_cast(this)); + + running = false; +} \ No newline at end of file diff --git a/PROJECTS/beginner/network-traffic-analyzer/cpp/src/cli/argsParse.cpp b/PROJECTS/beginner/network-traffic-analyzer/cpp/src/cli/argsParse.cpp new file mode 100644 index 00000000..3e10a120 --- /dev/null +++ b/PROJECTS/beginner/network-traffic-analyzer/cpp/src/cli/argsParse.cpp @@ -0,0 +1,64 @@ +#include "../../include/cli/argsParse.hpp" +#include + +argsParser::argsParser(int argc, char** argv) { + desc.add_options() + ("help,h", "Display this help message and exit") + ("interfaces, interfaces","Display all possible interfaces") + ("interface,i", po::value()->default_value("wlan0"), + "Network interface to capture packets from (e.g. eth0, wlan0, any)") + + ("count,c", po::value()->default_value(0), + "Number of packets to capture (0 = unlimited)") + ("time, t", po::value()->default_value(INT_MAX),"Working time (in seconds)") + + ("offline,r", po::value(), + "Read packets from an offline pcap file") + + ("filter,f", po::value>()->composing(), + "Traffic filter (can be used multiple times)\n" + " proto: tcp | udp | icmp | dns\n" + " src: Source IP address\n" + " dst: Destination IP address\n" + " port: Source or destination port") + + ("sort,s", po::value()->default_value("bytes"), + "Sort field: bytes | packets | ip") + + ("order,o", po::value()->default_value("desc"), + "Sort order: asc | desc") + + ("limit,n", po::value()->default_value(43), + "Limit number of displayed entries") + + ("csv", po::value(), + "Export analysis results to CSV file") + + ("json", po::value(), + "Export analysis results to JSON file"); + + po::store(po::parse_command_line(argc, argv, desc), vm); + po::notify(vm); +} + +void argsParser::print_help() const { + std::cout << + "Network Traffic Analyzer\n" + "========================\n\n" + "Usage:\n" + " ./network-traffic-analyzer [options]\n\n" + "Description:\n" + " Captures and analyzes network traffic from live interfaces or\n" + " offline pcap files. Provides protocol statistics, top talkers,\n" + " and bandwidth usage information.\n\n"; + + std::cout << desc << "\n"; + + std::cout << + "Examples:\n" + " ./network-traffic-analyzer -i wlan0 --count 100 --time 10\n" + " ./network-traffic-analyzer -i any --filter port:54\n" + " ./network-traffic-analyzer --offline traffic.pcap --json result.json\n\n"; + + std::cout << "To end the program, press 'q' or Esc to exit.\n"; +} \ No newline at end of file diff --git a/PROJECTS/beginner/network-traffic-analyzer/cpp/src/cli/filter.cpp b/PROJECTS/beginner/network-traffic-analyzer/cpp/src/cli/filter.cpp new file mode 100644 index 00000000..12dc1570 --- /dev/null +++ b/PROJECTS/beginner/network-traffic-analyzer/cpp/src/cli/filter.cpp @@ -0,0 +1,77 @@ +#include "../../include/cli/filter.hpp" + +#include + +filter parse(std::string str) { + auto pos = str.find(':'); + if (pos == std::string::npos) { + + } + + std::string type = str.substr(0, pos); + std::string value = str.substr(pos + 1); + + if (type == "protocol") return {PROTOCOL, value}; + if (type == "port") return {PORT, value}; + if (type == "dest") return {IP_DEST, value}; + if (type == "src") return {IP_SRC, value}; + if (type == "ip") return {IP_TYPE, value}; + return {NONE, value}; +} + +std::string get_bpf_filter(std::vector& f) { + std::map> groups; + + for (const auto& x : f) { + switch (x.type) { + case PROTOCOL: + if (x.val == "dns") + groups[PROTOCOL].push_back("port 53"); + else + groups[PROTOCOL].push_back(x.val); + break; + + case IP_DEST: + groups[IP_DEST].push_back("dst host " + x.val); + break; + + case IP_SRC: + groups[IP_SRC].push_back("src host " + x.val); + break; + + case PORT: + groups[PORT].push_back("port " + x.val); + break; + case IP_TYPE: + groups[IP_TYPE].push_back(x.val); + break; + + default: + break; + } + } + + std::string result; + bool first_group = true; + + for (auto& [type, parts] : groups) { + if (!first_group) + result += " and "; + first_group = false; + + if (parts.size() > 1) + result += "("; + + for (size_t i = 0; i < parts.size(); ++i) { + result += parts[i]; + if (i + 1 < parts.size()) + result += " or "; + } + + if (parts.size() > 1) + result += ")"; + } + + return result; +} + diff --git a/PROJECTS/beginner/network-traffic-analyzer/cpp/src/packet/IP.cpp b/PROJECTS/beginner/network-traffic-analyzer/cpp/src/packet/IP.cpp new file mode 100644 index 00000000..01e8164b --- /dev/null +++ b/PROJECTS/beginner/network-traffic-analyzer/cpp/src/packet/IP.cpp @@ -0,0 +1,211 @@ +#include "../../include/packet/IP.hpp" + +#include +#include +#include +#include +#include +#include + +uint16_t IP_class::get_payload_len() { + return payload_len; +} + + +/*** Ipv4 ***/ +IPv4::IPv4(const u_char* data) { + ip_hdr = reinterpret_cast(data); + ip_hdr_len = ip_hdr->ip_hl * 4; + if (ip_hdr_len < 20) { + fprintf(stderr, "Failed to initial IPv4 "); + } + + +} + +TransportProtocol IPv4::get_protocol() { + + switch (ip_hdr->ip_p) { + case IPPROTO_TCP: + handle_tcp(); + return TransportProtocol::TCP; + + case IPPROTO_UDP: + handle_udp(); + return TransportProtocol::UDP; + + case IPPROTO_ICMP: + handle_icmp(); + return TransportProtocol::ICMP; + + case IPPROTO_ICMPV6: + handle_icmpv6(); + return TransportProtocol::ICMP6; + + case IPPROTO_IGMP: + handle_igmp(); + return TransportProtocol::IGMP; + + default: + return TransportProtocol::UNKNOWN; + } +} +void IPv4::handle_tcp() { + auto* tcp = reinterpret_cast((u_char*)ip_hdr + ip_hdr_len); + + src_port = ntohs(tcp->source); + dest_port = ntohs(tcp->dest); + + payload_ptr = reinterpret_cast(tcp + tcp->doff * 4); + + payload_len = ntohs(ip_hdr->ip_len) - (ip_hdr_len + tcp->doff * 4); +} +void IPv4::handle_udp() { + auto* udp = reinterpret_cast((u_char*)ip_hdr + ip_hdr_len); + dest_port = ntohs(udp->dest); + src_port = ntohs(udp->source); + + payload_ptr = (u_char*)udp + sizeof(udphdr); + payload_len = ntohs(udp->len) - sizeof(udphdr); +} +void IPv4::handle_icmp() { + auto* icmp = reinterpret_cast((u_char*)ip_hdr + ip_hdr_len); + + +} +void IPv4::handle_icmpv6() { + auto* icmp = reinterpret_cast((u_char*)ip_hdr + ip_hdr_len); + +} +void IPv4::handle_igmp() { + auto* igmp = reinterpret_cast((u_char*)ip_hdr + ip_hdr_len); + +} + +std::string IPv4::get_source() { + return std::string(inet_ntoa(ip_hdr->ip_src)); +} +std::string IPv4::get_dest() { + return std::string(inet_ntoa(ip_hdr->ip_dst)); +} +uint16_t IPv4::get_src_port() { + return src_port; +} +uint16_t IPv4::get_dest_port() { + return dest_port; +} + +/*** Ipv6 ***/ + +IPv6::IPv6(const u_char* data) { + ip_hdr = reinterpret_cast(data); + +} + +TransportProtocol IPv6::get_protocol() { + uint8_t hdr = ip_hdr->ip6_nxt; + + ptr = reinterpret_cast(ip_hdr + 1); + while (true) { + switch (hdr) { + case IPPROTO_TCP: + handle_tcp(); + return TransportProtocol::TCP; + + case IPPROTO_UDP: + handle_udp(); + return TransportProtocol::UDP; + + case IPPROTO_ICMP: + handle_icmp(); + return TransportProtocol::ICMP; + + case IPPROTO_ICMPV6: + handle_icmpv6(); + return TransportProtocol::ICMP6; + + case IPPROTO_IGMP: + handle_igmp(); + return TransportProtocol::IGMP; + /* if we have an extension headers, dont leave from loop, + * keep find a protocol type + */ + case IPPROTO_HOPOPTS: + case IPPROTO_ROUTING: + case IPPROTO_DSTOPTS: { + const auto* ext = reinterpret_cast(ptr); + hdr = ext->ip6e_nxt; + ptr += (ext->ip6e_len + 1) * 8; + break; + } + case IPPROTO_FRAGMENT: { + + const auto* frag = reinterpret_cast(ptr); + hdr = frag->ip6f_nxt; + ptr += sizeof(ip6_frag); + break; + } + default: + return TransportProtocol::UNKNOWN; + } + } + ptr = nullptr; +} + +void IPv6::handle_tcp() { + const auto tcp = reinterpret_cast(ptr); + dest_port = ntohs(tcp->dest); + src_port = ntohs(tcp->source); + + payload_ptr = (const uint8_t*)tcp + tcp->doff * 4; + payload_len = ntohs(ip_hdr->ip6_plen) - tcp->doff * 4; + + ptr = nullptr; + +} +void IPv6::handle_udp() { + const auto udp = reinterpret_cast(ptr); + dest_port = ntohs(udp->dest); + src_port = ntohs(udp->source); + + payload_ptr = (const uint8_t*)udp + sizeof(udphdr); + payload_len = ntohs(udp->len) - sizeof(udphdr); + + ptr = nullptr; + +} +void IPv6::handle_icmp() { + + ptr = nullptr; +} +void IPv6::handle_icmpv6() { + payload_len = ntohs(ip_hdr->ip6_plen) - sizeof(icmp6_hdr); + ptr = nullptr; +} +void IPv6::handle_igmp() { + + ptr = nullptr; +} + +std::string IPv6::get_source() { + char src[INET6_ADDRSTRLEN]; + inet_ntop(AF_INET6, &ip_hdr->ip6_src, src, sizeof(src)); + return std::string(src); +} + +std::string IPv6::get_dest() { + char dst[INET6_ADDRSTRLEN]; + inet_ntop(AF_INET6, &ip_hdr->ip6_dst, dst, sizeof(dst)); + return std::string(dst); +} + +uint16_t IPv6::get_src_port() { + return src_port; +} + +uint16_t IPv6::get_dest_port() { + return dest_port; +} + + + diff --git a/PROJECTS/beginner/network-traffic-analyzer/cpp/src/packet/packet.cpp b/PROJECTS/beginner/network-traffic-analyzer/cpp/src/packet/packet.cpp new file mode 100644 index 00000000..11b1f81d --- /dev/null +++ b/PROJECTS/beginner/network-traffic-analyzer/cpp/src/packet/packet.cpp @@ -0,0 +1,49 @@ +#include "../../include/packet/packet.hpp" +#include + +ApplicationProtocol Packet::get_application_protocol() { + if (!payload_ptr || payload_len < 4) goto check_port; + + if (transport_protocol == TransportProtocol::TCP) { + if (!memcmp(payload_ptr, "GET ", 4) || + !memcmp(payload_ptr, "POST", 4) || + !memcmp(payload_ptr, "HEAD", 4) || + !memcmp(payload_ptr, "PUT ", 4) || + !memcmp(payload_ptr, "HTTP", 4)) + return ApplicationProtocol::HTTP; + } + + if ((src_port == 53 || dst_port == 53) && payload_len >= 12) { + return ApplicationProtocol::DNS; + } + if (transport_protocol == TransportProtocol::TCP && payload_len >= 3) { + if (payload_ptr[0] == 0x16 && payload_ptr[1] == 0x03) + return ApplicationProtocol::HTTPS; + } + +check_port: + uint16_t port = (src_port < dst_port) ? src_port : dst_port; + if (transport_protocol == TransportProtocol::TCP) { + switch (port) { + case 21: return ApplicationProtocol::FTP; + case 22: return ApplicationProtocol::SSH; + case 25: return ApplicationProtocol::SMTP; + case 53: return ApplicationProtocol::DNS; + case 80: return ApplicationProtocol::HTTP; + case 443: return ApplicationProtocol::HTTPS; + default: return ApplicationProtocol::UNKNOWN; + } + } + + if (transport_protocol == TransportProtocol::UDP) { + switch (port) { + case 53: return ApplicationProtocol::DNS; + case 443: return ApplicationProtocol::QUIC; + case 123: return ApplicationProtocol::NTP; + default: return ApplicationProtocol::UNKNOWN; + } + } + + return ApplicationProtocol::UNKNOWN; +} + diff --git a/PROJECTS/beginner/network-traffic-analyzer/cpp/src/stats/protocolStats.cpp b/PROJECTS/beginner/network-traffic-analyzer/cpp/src/stats/protocolStats.cpp new file mode 100644 index 00000000..4be6ca44 --- /dev/null +++ b/PROJECTS/beginner/network-traffic-analyzer/cpp/src/stats/protocolStats.cpp @@ -0,0 +1,421 @@ +#include "../../include/stats/protocolStats.hpp" + +#include "ftxui/dom/table.hpp" + + +Stats::Stats() { + last_tick = std::chrono::steady_clock::now(); +} +/** + * @brief Aggregates a newly captured packet. + * + * Updates: + * - Total packet/byte counters + * - Transport protocol stats + * - Application protocol stats + * - IP-level statistics + * - Communication pairs + * + * Must be called only from capture thread. + * Protected by mutex. + */ +void Stats::add_packet(Packet &packet) { + std::lock_guard lock(mtx); + + ++snapshot.total_p; + snapshot.total_b += packet.total_len; + + auto& t = transport_map[packet.transport_protocol]; + t.packets++; + t.bytes += packet.total_len; + + auto& a = application_map[packet.application_protocol]; + a.packets++; + a.bytes += packet.payload_len; + + ip_map[packet.src].packets_sent++; + ip_map[packet.src].bytes_sent += packet.total_len; + + ip_map[packet.dst].packets_received++; + ip_map[packet.dst].bytes_received += packet.total_len; + + auto key = std::make_pair(packet.src, packet.dst); + pairs[key].packets++; + pairs[key].bytes += packet.total_len; + +} + + +const char* transport_to_str(TransportProtocol p) { + switch (p) { + case TransportProtocol::TCP: return "TCP"; + case TransportProtocol::UDP: return "UDP"; + case TransportProtocol::ICMP: return "ICMP"; + case TransportProtocol::ICMP6: return "ICMP6"; + case TransportProtocol::IGMP: return "IGMP"; + default: return "UNKNOWN"; + } +} + +const char* app_to_str(ApplicationProtocol p) { + switch (p) { + case ApplicationProtocol::HTTP: return "HTTP"; + case ApplicationProtocol::HTTPS: return "HTTPS"; + case ApplicationProtocol::DNS: return "DNS"; + case ApplicationProtocol::FTP: return "FTP"; + case ApplicationProtocol::SSH: return "SSH"; + case ApplicationProtocol::SMTP: return "SMTP"; + case ApplicationProtocol::QUIC: return "QUIC"; + case ApplicationProtocol::NTP: return "NTP"; + default: return "UNKNOWN"; + } +} + +/** + * @brief Rebuilds transport protocol snapshot table. + * + * Sorts protocols by packet count (descending). + * Calculates percentage relative to total traffic. + * + * Called periodically by UI update thread. + */ + +void Stats::update_transport_stats() { + std::lock_guard lock(mtx); + snapshot.transport_rows.clear(); + snapshot.transport_rows.push_back({ "Proto", "Packets", "Bytes", "%" }); + + std::vector> tps( + transport_map.begin(), transport_map.end() + ); + std::sort(tps.begin(), tps.end(), + [](auto& a, auto& b) { + return a.second.packets > b.second.packets; + }); + + for (const auto& [proto, stats] : tps) { + double percent = snapshot.total_b ? stats.bytes * 100.0 / snapshot.total_b : 0.0; + snapshot.transport_rows.push_back({ + transport_to_str(proto), + std::to_string(stats.packets), + std::format("{:.2f}", stats.bytes / (1024.0 * 1024.0)), + std::format("{:.2f}", percent) + }); + + } +} + + +/** + * @brief Rebuilds application protocol snapshot. + * + * Sorted by packet count. + * Percent calculated relative to total bytes. + */ +void Stats::update_application_stats() { + std::lock_guard lock(mtx); + std::vector> apps( + application_map.begin(), application_map.end() + ); + + std::sort(apps.begin(), apps.end(), + [](auto& a, auto& b) { + return a.second.packets > b.second.packets; + }); + + snapshot.app_rows.clear(); + snapshot.app_rows.push_back({ "Proto", "Packets", "Bytes (MB)", "%" }); + + for (const auto& [proto, s] : apps) { + double percent = snapshot.total_b + ? s.bytes * 100.0 / snapshot.total_b + : 0.0; + + snapshot.app_rows.push_back({ + app_to_str(proto), + std::to_string(s.packets), + std::format("{:.2f}", s.bytes / (1024.0 * 1024.0)), + std::format("{:.2f}", percent) + }); + } +} + +/** + * @brief Generates snapshot for top IP addresses. + * + * @param limit Maximum number of IPs to display. + * + * Sorted by transmitted packets (descending). + */ +void Stats::update_ip_stats(size_t limit) { + std::lock_guard lock(mtx); + snapshot.rows.clear(); + + snapshot.rows.push_back({"IP Address", "Packets TX", "Packets RX"}); + std::vector> ips( + ip_map.begin(), ip_map.end() + ); + + std::sort(ips.begin(), ips.end(), + [](auto& a, auto& b) { + return a.second.packets_sent > b.second.packets_sent; + }); + + size_t count = 0; + for (const auto& [ip, s] : ips) { + if (count++ >= limit) break; + snapshot.rows.push_back({ip, + "TX: " + std::to_string(s.packets_sent), + "RX: " + std::to_string(s.packets_received)} + ); + + } +} + +/** + * @brief Builds snapshot of top communication pairs. + * + * @param limit Maximum number of pairs to include. + * + * Sorted by total bytes transferred. + */ + +void Stats::update_pairs(size_t limit) { + std::lock_guard lock(mtx); + std::vector, protocolStats>> vec(pairs.begin(), pairs.end()); + std::sort(vec.begin(), vec.end(), + [](auto& a, auto& b) { + return a.second.bytes > b.second.bytes; + }); + + snapshot.pairs_rows.clear(); + snapshot.pairs_rows.push_back({ "Source", "Destination", "bytes received", "%" }); + size_t count = 0; + for (const auto& [pair, s] : vec) { + + if (count++ >= limit) break; + double percent = snapshot.total_b ? (s.bytes * 100.0 / snapshot.total_b) : 0.0; + snapshot.pairs_rows.push_back({ + pair.first, + pair.second, + std::format("{:.0f}", s.bytes * 1.0), + std::format("{:.2f}", percent), + + }); + + } +} + +void Stats::update_packets(){ + std::lock_guard lock(mtx); + snapshot.packets_rows.clear(); + snapshot.packets_rows.push_back({ "IPVersion", "Transport protocol", "Source", "Destination", "App protocol"}); + + for (auto& packet : packets) { + snapshot.packets_rows.push_back({ + packet.ip_version == IPVersion::v4 ? "IPv4" : "IPv6", + transport_to_str(packet.transport_protocol), + packet.src, + packet.dst, + app_to_str(packet.application_protocol), + + }); + } +} + +double Stats::smooth_value(size_t i, size_t start) { + const int window = 3; + double sum = 0.0; + int count = 0; + + for (int k = -window; k <= window; ++k) { + long idx = (long)i + k; + if (idx >= (long)start && idx < (long)snapshot.bandwidth_history.size()) { + sum += snapshot.bandwidth_history[idx].bytes_per_sec; + count++; + } + } + return count ? sum / count : 0.0; +} +/** + * @brief Calculates current bandwidth (bytes/sec). + * + * Uses: + * - Delta bytes since last tick + * - Time elapsed + * - Exponential smoothing to reduce noise + * + * Stores history for graph rendering. + */ +void Stats::update_bandwidth() { + std::lock_guard lock(mtx); + using namespace std::chrono; + + auto now = steady_clock::now(); + double ts = duration_cast>(now.time_since_epoch()).count(); + double elapsed = duration_cast>(now - last_tick).count(); + + if (elapsed >= 1.0) { + + uint32_t delta_bytes = snapshot.total_b - last_b; + + snapshot.bandwidth = delta_bytes / elapsed; // bytes per second + + last_b = snapshot.total_b; + last_tick = now; + const double alpha = 0.2; + smooth_bandwidth = + alpha * snapshot.bandwidth + (1.0 - alpha) * smooth_bandwidth; + + snapshot.bandwidth_history.push_back({ ts, smooth_bandwidth }); + } + snapshot.max_bandwidth = std::max(snapshot.max_bandwidth, snapshot.bandwidth); + +} + +/** + * @brief Exports current statistics to CSV file. + * + * Includes: + * - Summary + * - Transport protocols + * - Application protocols + * - IP statistics + * - Bandwidth history + */ + +void Stats::export_csv(const std::string& filename) { + std::lock_guard lock(mtx); + std::ofstream file(filename); + if (!file.is_open()) return; + + file << "summary\n"; + file << "total_packets,total_bytes,bandwidth\n"; + file << snapshot.total_p << "," + << snapshot.total_b << "," + << snapshot.bandwidth << "\n\n"; + + // ===== Transport protocols ===== + file << "transport_protocols\n"; + file << "protocol,packets,bytes,percent\n"; + + for (const auto& [proto, s] : transport_map) { + double percent = snapshot.total_b ? (s.bytes * 100.0 / snapshot.total_b) : 0.0; + file << transport_to_str(proto) << "," + << s.packets << "," + << s.bytes << "," + << percent << "\n"; + } + file << "\n"; + + // ===== Application protocols ===== + file << "application_protocols\n"; + file << "protocol,packets,payload_bytes\n"; + + for (const auto& [proto, s] : application_map) { + file << static_cast(proto) << "," + << s.packets << "," + << s.bytes << "\n"; + } + file << "\n"; + + // ===== IP stats ===== + file << "ip_stats\n"; + file << "ip,packets_sent,packets_received,bytes_sent,bytes_received\n"; + + for (const auto& [ip, s] : ip_map) { + file << ip << "," + << s.packets_sent << "," + << s.packets_received << "," + << s.bytes_sent << "," + << s.bytes_received << "\n"; + } + + + //bandwidth + file << "time,bandwidth\n"; + + for (const auto& p : snapshot.bandwidth_history) { + file << p.timestamp << "," << p.bytes_per_sec << "\n"; + } + + file.close(); + +} +/** + * @brief Exports statistics to JSON format. + * + * Designed for: + * - External processing + * - Visualization tools + * - Data pipelines + */ +void Stats::export_json(const std::string& filename) { + std::lock_guard lock(mtx); + std::ofstream file(filename); + if (!file.is_open()) return; + + file << "{\n"; + + // ===== Summary ===== + file << " \"summary\": {\n"; + file << " \"total_packets\": " << snapshot.total_p << ",\n"; + file << " \"total_bytes\": " << snapshot.total_b << ",\n"; + file << " \"bandwidth\": " << snapshot.bandwidth << "\n"; + file << " },\n"; + + // ===== Transport ===== + file << " \"transport\": [\n"; + bool first = true; + for (const auto& [proto, s] : transport_map) { + if (!first) file << ",\n"; + first = false; + + double percent = snapshot.total_b ? (s.bytes * 100.0 / snapshot.total_b) : 0.0; + + file << " {\n"; + file << " \"protocol\": \"" << transport_to_str(proto) << "\",\n"; + file << " \"packets\": " << s.packets << ",\n"; + file << " \"bytes\": " << s.bytes << ",\n"; + file << " \"percent\": " << percent << "\n"; + file << " }"; + } + file << "\n ],\n"; + + // ===== IP stats ===== + file << " \"top_ips\": [\n"; + first = true; + for (const auto& [ip, s] : ip_map) { + if (!first) file << ",\n"; + first = false; + + file << " {\n"; + file << " \"ip\": \"" << ip << "\",\n"; + file << " \"packets_sent\": " << s.packets_sent << ",\n"; + file << " \"packets_received\": " << s.packets_received << ",\n"; + file << " \"bytes_sent\": " << s.bytes_sent << ",\n"; + file << " \"bytes_received\": " << s.bytes_received << "\n"; + file << " }"; + } + file << "\n ]\n"; + + file << ",\n \"communication_pairs\": [\n"; + first = true; + + for (const auto& [pair, s] : pairs) { + if (!first) file << ",\n"; + first = false; + + file << " {\n"; + file << " \"src\": \"" << pair.first << "\",\n"; + file << " \"dst\": \"" << pair.second << "\",\n"; + file << " \"packets\": " << s.packets << ",\n"; + file << " \"bytes\": " << s.bytes << "\n"; + file << " }"; + } + + file << "\n ]"; + + file << "}\n"; + file.close(); +} diff --git a/PROJECTS/beginner/network-traffic-analyzer/LICENSE b/PROJECTS/beginner/network-traffic-analyzer/python/LICENSE similarity index 100% rename from PROJECTS/beginner/network-traffic-analyzer/LICENSE rename to PROJECTS/beginner/network-traffic-analyzer/python/LICENSE diff --git a/PROJECTS/beginner/network-traffic-analyzer/README.md b/PROJECTS/beginner/network-traffic-analyzer/python/README.md similarity index 100% rename from PROJECTS/beginner/network-traffic-analyzer/README.md rename to PROJECTS/beginner/network-traffic-analyzer/python/README.md diff --git a/PROJECTS/beginner/network-traffic-analyzer/justfile b/PROJECTS/beginner/network-traffic-analyzer/python/justfile similarity index 100% rename from PROJECTS/beginner/network-traffic-analyzer/justfile rename to PROJECTS/beginner/network-traffic-analyzer/python/justfile diff --git a/PROJECTS/beginner/network-traffic-analyzer/learn/00-OVERVIEW.md b/PROJECTS/beginner/network-traffic-analyzer/python/learn/00-OVERVIEW.md similarity index 100% rename from PROJECTS/beginner/network-traffic-analyzer/learn/00-OVERVIEW.md rename to PROJECTS/beginner/network-traffic-analyzer/python/learn/00-OVERVIEW.md diff --git a/PROJECTS/beginner/network-traffic-analyzer/learn/01-CONCEPTS.md b/PROJECTS/beginner/network-traffic-analyzer/python/learn/01-CONCEPTS.md similarity index 100% rename from PROJECTS/beginner/network-traffic-analyzer/learn/01-CONCEPTS.md rename to PROJECTS/beginner/network-traffic-analyzer/python/learn/01-CONCEPTS.md diff --git a/PROJECTS/beginner/network-traffic-analyzer/learn/02-ARCHITECTURE.md b/PROJECTS/beginner/network-traffic-analyzer/python/learn/02-ARCHITECTURE.md similarity index 100% rename from PROJECTS/beginner/network-traffic-analyzer/learn/02-ARCHITECTURE.md rename to PROJECTS/beginner/network-traffic-analyzer/python/learn/02-ARCHITECTURE.md diff --git a/PROJECTS/beginner/network-traffic-analyzer/learn/03-IMPLEMENTATION.md b/PROJECTS/beginner/network-traffic-analyzer/python/learn/03-IMPLEMENTATION.md similarity index 100% rename from PROJECTS/beginner/network-traffic-analyzer/learn/03-IMPLEMENTATION.md rename to PROJECTS/beginner/network-traffic-analyzer/python/learn/03-IMPLEMENTATION.md diff --git a/PROJECTS/beginner/network-traffic-analyzer/learn/04-CHALLENGES.md b/PROJECTS/beginner/network-traffic-analyzer/python/learn/04-CHALLENGES.md similarity index 100% rename from PROJECTS/beginner/network-traffic-analyzer/learn/04-CHALLENGES.md rename to PROJECTS/beginner/network-traffic-analyzer/python/learn/04-CHALLENGES.md diff --git a/PROJECTS/beginner/network-traffic-analyzer/pyproject.toml b/PROJECTS/beginner/network-traffic-analyzer/python/pyproject.toml similarity index 100% rename from PROJECTS/beginner/network-traffic-analyzer/pyproject.toml rename to PROJECTS/beginner/network-traffic-analyzer/python/pyproject.toml diff --git a/PROJECTS/beginner/network-traffic-analyzer/src/netanal/__init__.py b/PROJECTS/beginner/network-traffic-analyzer/python/src/netanal/__init__.py similarity index 100% rename from PROJECTS/beginner/network-traffic-analyzer/src/netanal/__init__.py rename to PROJECTS/beginner/network-traffic-analyzer/python/src/netanal/__init__.py diff --git a/PROJECTS/beginner/network-traffic-analyzer/src/netanal/__main__.py b/PROJECTS/beginner/network-traffic-analyzer/python/src/netanal/__main__.py similarity index 100% rename from PROJECTS/beginner/network-traffic-analyzer/src/netanal/__main__.py rename to PROJECTS/beginner/network-traffic-analyzer/python/src/netanal/__main__.py diff --git a/PROJECTS/beginner/network-traffic-analyzer/src/netanal/analyzer.py b/PROJECTS/beginner/network-traffic-analyzer/python/src/netanal/analyzer.py similarity index 100% rename from PROJECTS/beginner/network-traffic-analyzer/src/netanal/analyzer.py rename to PROJECTS/beginner/network-traffic-analyzer/python/src/netanal/analyzer.py diff --git a/PROJECTS/beginner/network-traffic-analyzer/src/netanal/capture.py b/PROJECTS/beginner/network-traffic-analyzer/python/src/netanal/capture.py similarity index 100% rename from PROJECTS/beginner/network-traffic-analyzer/src/netanal/capture.py rename to PROJECTS/beginner/network-traffic-analyzer/python/src/netanal/capture.py diff --git a/PROJECTS/beginner/network-traffic-analyzer/src/netanal/constants.py b/PROJECTS/beginner/network-traffic-analyzer/python/src/netanal/constants.py similarity index 100% rename from PROJECTS/beginner/network-traffic-analyzer/src/netanal/constants.py rename to PROJECTS/beginner/network-traffic-analyzer/python/src/netanal/constants.py diff --git a/PROJECTS/beginner/network-traffic-analyzer/src/netanal/exceptions.py b/PROJECTS/beginner/network-traffic-analyzer/python/src/netanal/exceptions.py similarity index 100% rename from PROJECTS/beginner/network-traffic-analyzer/src/netanal/exceptions.py rename to PROJECTS/beginner/network-traffic-analyzer/python/src/netanal/exceptions.py diff --git a/PROJECTS/beginner/network-traffic-analyzer/src/netanal/export.py b/PROJECTS/beginner/network-traffic-analyzer/python/src/netanal/export.py similarity index 100% rename from PROJECTS/beginner/network-traffic-analyzer/src/netanal/export.py rename to PROJECTS/beginner/network-traffic-analyzer/python/src/netanal/export.py diff --git a/PROJECTS/beginner/network-traffic-analyzer/src/netanal/filters.py b/PROJECTS/beginner/network-traffic-analyzer/python/src/netanal/filters.py similarity index 100% rename from PROJECTS/beginner/network-traffic-analyzer/src/netanal/filters.py rename to PROJECTS/beginner/network-traffic-analyzer/python/src/netanal/filters.py diff --git a/PROJECTS/beginner/network-traffic-analyzer/src/netanal/main.py b/PROJECTS/beginner/network-traffic-analyzer/python/src/netanal/main.py similarity index 100% rename from PROJECTS/beginner/network-traffic-analyzer/src/netanal/main.py rename to PROJECTS/beginner/network-traffic-analyzer/python/src/netanal/main.py diff --git a/PROJECTS/beginner/network-traffic-analyzer/src/netanal/models.py b/PROJECTS/beginner/network-traffic-analyzer/python/src/netanal/models.py similarity index 100% rename from PROJECTS/beginner/network-traffic-analyzer/src/netanal/models.py rename to PROJECTS/beginner/network-traffic-analyzer/python/src/netanal/models.py diff --git a/PROJECTS/beginner/network-traffic-analyzer/src/netanal/output.py b/PROJECTS/beginner/network-traffic-analyzer/python/src/netanal/output.py similarity index 100% rename from PROJECTS/beginner/network-traffic-analyzer/src/netanal/output.py rename to PROJECTS/beginner/network-traffic-analyzer/python/src/netanal/output.py diff --git a/PROJECTS/beginner/network-traffic-analyzer/src/netanal/statistics.py b/PROJECTS/beginner/network-traffic-analyzer/python/src/netanal/statistics.py similarity index 100% rename from PROJECTS/beginner/network-traffic-analyzer/src/netanal/statistics.py rename to PROJECTS/beginner/network-traffic-analyzer/python/src/netanal/statistics.py diff --git a/PROJECTS/beginner/network-traffic-analyzer/src/netanal/visualization.py b/PROJECTS/beginner/network-traffic-analyzer/python/src/netanal/visualization.py similarity index 100% rename from PROJECTS/beginner/network-traffic-analyzer/src/netanal/visualization.py rename to PROJECTS/beginner/network-traffic-analyzer/python/src/netanal/visualization.py diff --git a/PROJECTS/beginner/network-traffic-analyzer/tests/__init__.py b/PROJECTS/beginner/network-traffic-analyzer/python/tests/__init__.py similarity index 100% rename from PROJECTS/beginner/network-traffic-analyzer/tests/__init__.py rename to PROJECTS/beginner/network-traffic-analyzer/python/tests/__init__.py diff --git a/PROJECTS/beginner/network-traffic-analyzer/tests/test_filters.py b/PROJECTS/beginner/network-traffic-analyzer/python/tests/test_filters.py similarity index 100% rename from PROJECTS/beginner/network-traffic-analyzer/tests/test_filters.py rename to PROJECTS/beginner/network-traffic-analyzer/python/tests/test_filters.py diff --git a/PROJECTS/beginner/network-traffic-analyzer/tests/test_models.py b/PROJECTS/beginner/network-traffic-analyzer/python/tests/test_models.py similarity index 100% rename from PROJECTS/beginner/network-traffic-analyzer/tests/test_models.py rename to PROJECTS/beginner/network-traffic-analyzer/python/tests/test_models.py diff --git a/PROJECTS/beginner/network-traffic-analyzer/uv.lock b/PROJECTS/beginner/network-traffic-analyzer/python/uv.lock similarity index 100% rename from PROJECTS/beginner/network-traffic-analyzer/uv.lock rename to PROJECTS/beginner/network-traffic-analyzer/python/uv.lock