Skip to content
API Documentation
BastionGuard™
ConnectionsPage.hpp
Go to the documentation of this file.
1/*
2 * BastionGuard™ Secure Connection
3 * Copyright (C) 2025–2026 Calogero Scarnà
4 * GPL-3.0 — BastionGuard™ is a trademark of Calogero Scarnà.
5 */
6
7#pragma once
8#include <gtkmm.h>
9#include <string>
10#include <cstdint>
11#include <vector>
12#include <deque>
13#include <unordered_map>
14#include <chrono>
15
16#include "../ipc/UIServer.hpp"
17
18/*
19 * ConnectionsPage v3 — vista stile Little Snitch
20 * ================================================
21 *
22 * La pagina ha due stati navigabili, incapsulati in un Gtk::Stack:
23 *
24 * [stato "apps"] (default)
25 * Lista aggregata per applicazione. Una riga per app.
26 * Colonne: Applicazione | Connessioni | Dati stimati | Ultimo accesso
27 * Double-click o invio → passa allo stato "detail" filtrato su quell'app.
28 *
29 * [stato "detail"]
30 * Lista dettagliata delle connessioni di UNA sola app.
31 * Pulsante ← Indietro in toolbar → torna a "apps".
32 * Stesse colonne della v2: Ora | Applicazione | Esito | # | PID | ...
33 *
34 * In basso, sempre visibile: sparkline del throughput STIMATO degli ultimi
35 * 60 secondi. La stima si basa su un costo byte fisso per connessione
36 * (kEstimatedBytesPerConn) — volutamente approssimativa, comunicata come
37 * "stima" nell'etichetta del grafico.
38 */
39
40class ConnectionsPage : public Gtk::Box
41{
42public:
43 explicit ConnectionsPage(UIServer& uiServer);
44 ~ConnectionsPage() override;
45
46 /** Aggiunge una riga con tutti i dati eBPF.
47 * Se esiste gia' una riga con stesso (app, host/ip, port, proto)
48 * incrementa il contatore invece di creare un duplicato.
49 * Aggiorna anche i contatori aggregati per-app e lo sparkline. */
50 void onNewConnection(const ConnectionInfo& info, bool pending = true);
51
52 /** Aggiorna il verdetto dell'ultima riga pending. */
53 void setLastVerdict(const std::string& verdict);
54
55 void saveLog() const;
56 void loadLog();
57 void clearLog();
58 static std::string logPath();
59
60 // Stima: ogni connessione pesa mediamente ~4 KB (handshake + piccolo
61 // scambio dati tipico per web/DNS/metriche). E' volutamente conservativa:
62 // i trasferimenti pesanti (video, download) sono l'eccezione e non
63 // intendiamo stimarli qui — per i valori reali servirebbe eBPF
64 // tcp_sendmsg/udp_sendmsg con accumulo byte, esplicitamente scartato
65 // dall'utente (leggerezza > precisione).
66 static constexpr uint64_t kEstimatedBytesPerConn = 4096;
67
68private:
69 // ── Colonne vista "Connessioni dettagliate" ────────────────────────
70 struct DetailColumns : public Gtk::TreeModel::ColumnRecord {
72 add(col_time);
73 add(col_app);
74 add(col_verdict);
75 add(col_count);
76 add(col_pid);
77 add(col_protocol);
78 add(col_src_ip);
79 add(col_host);
80 add(col_ip);
81 add(col_port);
82 add(col_args);
83 add(col_pending);
84 add(col_group_key);
85 }
86 Gtk::TreeModelColumn<Glib::ustring> col_time;
87 Gtk::TreeModelColumn<Glib::ustring> col_app;
88 Gtk::TreeModelColumn<Glib::ustring> col_verdict;
89 Gtk::TreeModelColumn<uint32_t> col_count;
90 Gtk::TreeModelColumn<uint32_t> col_pid;
91 Gtk::TreeModelColumn<Glib::ustring> col_protocol;
92 Gtk::TreeModelColumn<Glib::ustring> col_src_ip;
93 Gtk::TreeModelColumn<Glib::ustring> col_host;
94 Gtk::TreeModelColumn<Glib::ustring> col_ip;
95 Gtk::TreeModelColumn<uint32_t> col_port;
96 Gtk::TreeModelColumn<Glib::ustring> col_args;
97 Gtk::TreeModelColumn<bool> col_pending;
98 Gtk::TreeModelColumn<Glib::ustring> col_group_key;
99 };
100
101 // ── Colonne vista "App aggregate" ──────────────────────────────────
102 struct AppColumns : public Gtk::TreeModel::ColumnRecord {
104 add(col_icon_name); // icona da tema ("firefox", "application-x-executable")
105 add(col_app);
106 add(col_conn_count);
107 add(col_bytes_str); // "1.2 MB" formattato
108 add(col_bytes_raw); // byte grezzi per sort
109 add(col_last_seen);
110 add(col_last_ts); // epoch sec per sort
111 }
112 Gtk::TreeModelColumn<Glib::ustring> col_icon_name;
113 Gtk::TreeModelColumn<Glib::ustring> col_app;
114 Gtk::TreeModelColumn<uint32_t> col_conn_count;
115 Gtk::TreeModelColumn<Glib::ustring> col_bytes_str;
116 Gtk::TreeModelColumn<uint64_t> col_bytes_raw;
117 Gtk::TreeModelColumn<Glib::ustring> col_last_seen;
118 Gtk::TreeModelColumn<int64_t> col_last_ts;
119 };
120
121 struct ComboCol : public Gtk::TreeModel::ColumnRecord {
122 ComboCol() { add(val); }
123 Gtk::TreeModelColumn<Glib::ustring> val;
124 };
125
126 // ── Stato aggregato per-app ────────────────────────────────────────
127 struct AppStats {
128 uint32_t conn_count = 0;
129 uint64_t estimated_bytes = 0;
130 int64_t last_seen_epoch = 0; // per sort / display
131 };
132
133 // ── Evento detail salvato nel ring buffer per-app ──────────────────
134 // Usato per persistere gli ultimi 500 eventi detail per ciascuna app:
135 // al secondo avvio, aprendo un'app si vede lo storico fino al limite
136 // invece della lista vuota. Struttura pensata per essere serializzata
137 // direttamente in JSONL.
138 struct DetailEvent {
139 std::string time; // "HH:MM:SS" quando e' arrivato
140 int64_t ts_epoch{0}; // per retention 7 giorni
141 uint32_t pid{0};
142 std::string protocol;
143 std::string src_ip;
144 std::string host;
145 std::string ip;
146 uint32_t port{0};
147 std::string args;
148 std::string verdict; // "✅ Consentito...", "❌ Negato..." o ""
149 bool pending{false};
150 uint32_t count{1}; // repetizioni dedup
151 };
152
153 static constexpr size_t kDetailRingMax = 500;
154 // app_display → coda FIFO degli ultimi kDetailRingMax eventi.
155 // Niente mutex: tutto nel main thread GTK.
156 std::unordered_map<std::string, std::deque<DetailEvent>> m_detailRings;
157
158 // Retention log: eventi/aggregati piu' vecchi di 7 giorni scartati
159 // al loadLog(). Nessun cleanup periodico: se l'app sta aperta 10
160 // giorni di fila il file semplicemente cresce — saveLog() lo
161 // riscrive ogni volta, quindi ridimensiona naturalmente.
162 static constexpr int64_t kRetentionSeconds = 7 * 24 * 3600;
163
164 // ── Campione sparkline ─────────────────────────────────────────────
165 // Ogni tick (500 ms) salviamo un bucket con i byte stimati generati
166 // nell'intervallo. 120 bucket da 500ms = 60 secondi di storia.
167 static constexpr int kSparkBucketMs = 500;
168 static constexpr int kSparkBucketsMax = 120;
169 std::deque<uint64_t> m_sparkBuckets; // byte per bucket (piu' vecchio in front)
170 uint64_t m_currentBucket = 0;
171 sigc::connection m_sparkTick;
172
173 // ── UI ─────────────────────────────────────────────────────────────
177 Glib::RefPtr<Gtk::ListStore> m_verdictModel;
178 Glib::RefPtr<Gtk::ListStore> m_store; // detail
179 Glib::RefPtr<Gtk::ListStore> m_appStore; // apps aggregate
180 Gtk::TreeView m_treeView; // detail
181 Gtk::TreeView m_appTreeView; // apps
182 Gtk::ScrolledWindow m_scroll;
183 Gtk::ScrolledWindow m_appScroll;
184
185 // Stack: due pagine ("apps" / "detail")
186 Gtk::Stack m_stack;
187
188 // Toolbar superiore (filtro + pulsanti)
189 Gtk::Box m_toolbar { Gtk::Orientation::HORIZONTAL, 6 };
190 Gtk::Button m_clearBtn;
191 Gtk::Button m_backBtn; // <- Indietro (visibile solo in detail)
192 Gtk::Label m_detailTitle; // "Connessioni di <app>"
193 Gtk::SearchEntry m_search;
194
195 // Sparkline area
196 Gtk::Frame m_sparkFrame;
197 Gtk::Box m_sparkBox { Gtk::Orientation::VERTICAL, 4 };
198 Gtk::Label m_sparkLabel;
199 Gtk::DrawingArea m_sparkArea;
200
201 // Indice rapido chiave di gruppo → iteratore della riga detail.
202 std::unordered_map<std::string, Gtk::TreeModel::iterator> m_groupIndex;
203
204 // Indice app → statistiche aggregate + iteratore nella lista apps
205 std::unordered_map<std::string, AppStats> m_appStats;
206 std::unordered_map<std::string, Gtk::TreeModel::iterator> m_appIndex;
207
208 // Quando siamo nella vista "detail", teniamo traccia dell'app filtrata:
209 // le connessioni di altre app NON vengono aggiunte al m_store corrente
210 // (restano comunque contate negli aggregati e nello sparkline).
212
213 // Cache app_display → icon-name (evita di ricostruire il lookup ad
214 // ogni connessione). Popolata da resolveIconName().
215 std::unordered_map<std::string, std::string> m_iconCache;
216
217 // Indice comm/exec lowercase → icon-name dal sistema. Popolato una
218 // sola volta al primo bisogno (costruirlo richiede di scandire tutti
219 // i .desktop installati — ~100 ms).
220 std::unordered_map<std::string, std::string> m_desktopIndex;
222
224
225 void buildVerdictModel();
226 void buildToolbar();
227 void buildAppTreeView();
228 void buildDetailTreeView();
229 void buildSparkline();
230
231 // Naviga tra vista apps e vista detail
232 void showAppsView();
233 void showDetailView(const std::string& app);
234
235 void onAppActivated(const Gtk::TreeModel::Path& path,
236 Gtk::TreeViewColumn* column);
237 void onBackClicked();
238 void onVerdictEdited(const Glib::ustring& pathStr, const Glib::ustring& newText);
239 void onClear();
240
241 // Inserisce o aggiorna la riga detail — solo se l'app matcha il
242 // filtro corrente (in vista detail). Ritorna true se ha aggiunto/
243 // aggiornato qualcosa, false se ha solo aggiornato il contatore.
244 void addOrUpdateDetailRow(const ConnectionInfo& info,
245 const std::string& app_display,
246 const std::string& argsStr,
247 bool pending);
248
249 // Aggiorna i contatori aggregati per-app (la tabella apps)
250 void updateAppAggregate(const std::string& app_display);
251
252 // Push di un evento detail nel ring buffer dell'app indicata.
253 // Enforce kDetailRingMax: se pieno, evinciamo il piu' vecchio.
254 void pushDetailEvent(const std::string& app_display,
255 const DetailEvent& ev);
256
257 // Popola m_store leggendo gli eventi dal ring buffer di un'app.
258 // Usato quando l'utente entra in vista detail di un'app che ha
259 // storico persistito dal precedente avvio.
260 void populateDetailFromRing(const std::string& app_display);
261
262 // Sparkline: callback timer (ogni kSparkBucketMs ms)
263 bool onSparkTick();
264 void onSparkDraw(const Cairo::RefPtr<Cairo::Context>& cr,
265 int width, int height);
266
267 // Risolve un nome di processo (comm/path) in un icon-name del tema
268 // GTK. Prima cerca tra i .desktop installati (via DesktopAppInfo),
269 // poi prova il nome stesso come icon-name (molti pacchetti
270 // installano icone dal nome identico al binario), infine fallback a
271 // "application-x-executable". Risultato cached in m_iconCache.
272 std::string resolveIconName(const std::string& app_display);
273
274 // Costruisce l'indice comm/exec → icon-name scorrendo una sola
275 // volta tutti i .desktop registrati nel sistema. Chiamato al primo
276 // bisogno (lazy).
277 void buildDesktopIndex();
278
279 std::string currentTime();
280 ReplyAction parseReplyAction(const Glib::ustring& text) const;
281
282 // Calcola la chiave di dedup per una connessione (detail dedup).
283 static std::string groupKeyFor(const std::string& app,
284 const std::string& protocol,
285 const std::string& host,
286 const std::string& ip,
287 uint32_t port);
288
289 // Formatta byte grezzi in stringa umana: "42 B", "1.2 KB", "3.4 MB"
290 static std::string formatBytes(uint64_t n);
291
292 // Formatta un epoch in "HH:MM:SS" per la colonna "Ultimo accesso"
293 static std::string formatEpochTime(int64_t epoch);
294
295 // Helper log JSON
296 static std::string jsonEscapeStr(const std::string& s);
297 static std::string jsonFieldStr(const std::string& json, const std::string& key);
298 static uint32_t jsonFieldUint(const std::string& json, const std::string& key);
299 static bool jsonFieldBool(const std::string& json, const std::string& key);
300};
nlohmann::json json
Definition Backend.cpp:2908
ReplyAction
Definition UIServer.hpp:42
void onAppActivated(const Gtk::TreeModel::Path &path, Gtk::TreeViewColumn *column)
std::deque< uint64_t > m_sparkBuckets
Gtk::TreeView m_appTreeView
Gtk::ScrolledWindow m_scroll
std::unordered_map< std::string, std::string > m_desktopIndex
Gtk::TreeView m_treeView
Gtk::ScrolledWindow m_appScroll
ConnectionsPage(UIServer &uiServer)
DetailColumns m_columns
Glib::RefPtr< Gtk::ListStore > m_appStore
void onSparkDraw(const Cairo::RefPtr< Cairo::Context > &cr, int width, int height)
static constexpr uint64_t kEstimatedBytesPerConn
void setLastVerdict(const std::string &verdict)
void pushDetailEvent(const std::string &app_display, const DetailEvent &ev)
std::unordered_map< std::string, std::string > m_iconCache
static std::string groupKeyFor(const std::string &app, const std::string &protocol, const std::string &host, const std::string &ip, uint32_t port)
Gtk::DrawingArea m_sparkArea
std::string currentTime()
Glib::RefPtr< Gtk::ListStore > m_store
void onVerdictEdited(const Glib::ustring &pathStr, const Glib::ustring &newText)
static std::string formatEpochTime(int64_t epoch)
std::unordered_map< std::string, Gtk::TreeModel::iterator > m_appIndex
Glib::RefPtr< Gtk::ListStore > m_verdictModel
std::unordered_map< std::string, std::deque< DetailEvent > > m_detailRings
static std::string formatBytes(uint64_t n)
std::unordered_map< std::string, AppStats > m_appStats
static std::string jsonEscapeStr(const std::string &s)
static bool jsonFieldBool(const std::string &json, const std::string &key)
sigc::connection m_sparkTick
void onNewConnection(const ConnectionInfo &info, bool pending=true)
static std::string jsonFieldStr(const std::string &json, const std::string &key)
std::string m_currentAppFilter
static constexpr int kSparkBucketMs
void populateDetailFromRing(const std::string &app_display)
void updateAppAggregate(const std::string &app_display)
void addOrUpdateDetailRow(const ConnectionInfo &info, const std::string &app_display, const std::string &argsStr, bool pending)
std::unordered_map< std::string, Gtk::TreeModel::iterator > m_groupIndex
void showDetailView(const std::string &app)
static constexpr int kSparkBucketsMax
ReplyAction parseReplyAction(const Glib::ustring &text) const
Gtk::SearchEntry m_search
static constexpr int64_t kRetentionSeconds
static std::string logPath()
static constexpr size_t kDetailRingMax
std::string resolveIconName(const std::string &app_display)
static uint32_t jsonFieldUint(const std::string &json, const std::string &key)
Gtk::TreeModelColumn< Glib::ustring > col_icon_name
Gtk::TreeModelColumn< int64_t > col_last_ts
Gtk::TreeModelColumn< Glib::ustring > col_last_seen
Gtk::TreeModelColumn< uint32_t > col_conn_count
Gtk::TreeModelColumn< uint64_t > col_bytes_raw
Gtk::TreeModelColumn< Glib::ustring > col_bytes_str
Gtk::TreeModelColumn< Glib::ustring > col_app
Gtk::TreeModelColumn< Glib::ustring > val
Gtk::TreeModelColumn< Glib::ustring > col_protocol
Gtk::TreeModelColumn< Glib::ustring > col_ip
Gtk::TreeModelColumn< Glib::ustring > col_src_ip
Gtk::TreeModelColumn< Glib::ustring > col_group_key
Gtk::TreeModelColumn< uint32_t > col_port
Gtk::TreeModelColumn< Glib::ustring > col_time
Gtk::TreeModelColumn< Glib::ustring > col_args
Gtk::TreeModelColumn< Glib::ustring > col_host
Gtk::TreeModelColumn< bool > col_pending
Gtk::TreeModelColumn< Glib::ustring > col_verdict
Gtk::TreeModelColumn< Glib::ustring > col_app
Gtk::TreeModelColumn< uint32_t > col_count
Gtk::TreeModelColumn< uint32_t > col_pid