Skip to content
API Documentation
BastionGuard™
DaemonService.cpp
Go to the documentation of this file.
3#include <cctype>
4#include <chrono>
5#include <filesystem>
6#include <fstream>
7#include <nlohmann/json.hpp>
8#include <sstream>
9#include <thread>
10#include <libintl.h>
11
12#define _(text) gettext(text)
13#define N_(text) text
14
15namespace bsc::service {
16namespace fs = std::filesystem;
17using json = nlohmann::json;
18namespace {
19constexpr auto kRecentFlowTtl = std::chrono::seconds(10);
20
21std::string lower_copy(std::string s) {
22 for (char& ch : s) ch = static_cast<char>(std::tolower(static_cast<unsigned char>(ch)));
23 return s;
24}
25
26bool looks_like_browser_thread_comm(const std::string& comm) {
27 const auto s = lower_copy(comm);
28 return s.find("thread") != std::string::npos ||
29 s.find("socket") != std::string::npos ||
30 s.find("web content") != std::string::npos ||
31 s.find("renderer") != std::string::npos ||
32 s.find("utility") != std::string::npos ||
33 s.find("chrome_") != std::string::npos ||
34 s.find("isolated web") != std::string::npos ||
35 s == "gpu process" ||
36 s == "rdd process" ||
37 s == "privileged cont";
38}
39
41 return c.process_id != 0 ||
42 !c.process_path.empty() ||
44}
45
47 if (conn.process_id == 0) conn.process_id = static_cast<std::uint32_t>(p.pid);
48 // /proc/<tgid>/comm è il nome del processo principale. Preferiscilo
49 // sempre al task comm catturato da eBPF, che nei browser spesso è il
50 // nome del thread (es. "Socket Thread", "Chrome_ChildIOT").
51 if (!p.comm.empty()) conn.process_comm = p.comm;
52 if (!p.path.empty()) conn.process_path = p.path;
53 if (conn.process_cwd.empty()) conn.process_cwd = p.cwd;
54 if (conn.process_args.empty()) conn.process_args = p.args;
55 if (conn.process_env.empty()) conn.process_env = p.env;
56 if (conn.process_checksums.empty()) conn.process_checksums = p.checksums;
57 if (conn.process_tree.empty()) conn.process_tree = p.process_tree;
58 if (conn.user_id == 0xffffffffu) conn.user_id = static_cast<std::uint32_t>(p.uid);
59}
60
62 if (!a.path.empty() && !b.path.empty()) return a.path == b.path;
63 return !a.comm.empty() && !b.comm.empty() && a.comm == b.comm;
64}
65}
66
67std::chrono::milliseconds DaemonService::parse_interval(const std::string& v, std::chrono::milliseconds def) {
68 if (v.empty()) return def;
69 try {
70 if (v.ends_with("ms")) return std::chrono::milliseconds(std::stoll(v.substr(0, v.size() - 2)));
71 if (v.ends_with("s")) return std::chrono::seconds(std::stoll(v.substr(0, v.size() - 1)));
72 if (v.ends_with("m")) return std::chrono::minutes(std::stoll(v.substr(0, v.size() - 1)));
73 } catch (...) {}
74 return def;
75}
76
78 : config_(std::move(config)), firewall_(config_.firewall), stats_(config_.stats.max_events), ui_(config_.server.address) {
79 const auto aliases = std::filesystem::path(config_.rules.path).parent_path() / "network_aliases.json";
80 rules_.load_aliases(aliases.string());
83}
84
87
88 // Registra l'handler per le notifiche in arrivo dalla UI (cambio config,
89 // stop, reload regole...). Viene chiamato dal thread reader del
90 // UIClient — deve restare breve.
93 });
94
95 // Avvia il thread di connessione/riconnessione in background. Non-blocca:
96 // se la UI non e' ancora su (es. daemon partito dal gestore servizi,
97 // utente non ancora loggato), il reconnector interno ritentera' ogni 1s
98 // finche' la UI non compare.
99 ui_.start();
100 log.info("UIClient avviato verso " + config_.server.address);
101
103
104 auto scan_interval = std::chrono::seconds(2);
105 auto heartbeat_interval = std::chrono::seconds(10);
106 auto rules_interval = std::chrono::seconds(3);
107 auto firewall_interval = parse_interval(config_.fw_options.monitor_interval, std::chrono::seconds(15));
108
109 const fs::path tasks_file = fs::path(config_.tasks.config_path) / "tasks.json";
110 if (fs::exists(tasks_file)) {
111 try {
112 std::ifstream in(tasks_file);
113 json j;
114 in >> j;
115 if (j.contains("tasks") && j["tasks"].is_array()) {
116 for (const auto& task : j["tasks"]) {
117 const auto name = task.value("name", std::string{});
118 const auto interval = parse_interval(task.value("interval", std::string{"5s"}), std::chrono::seconds(5));
119 if (name == "scan") {
120 scan_interval = std::chrono::duration_cast<std::chrono::seconds>(interval);
121 } else if (name == "heartbeat") {
122 heartbeat_interval = std::chrono::duration_cast<std::chrono::seconds>(interval);
123 } else if (name == "rules_reload") {
124 rules_interval = std::chrono::duration_cast<std::chrono::seconds>(interval);
125 }
126 else if (name == "firewall_reload") firewall_interval = interval;
127 }
128 }
129 } catch (...) {
130 log.warning("tasks.json non valido, uso gli intervalli di default");
131 }
132 }
133
134 tasks_.every("scan", scan_interval, [this]() { scan_connections(); });
135 tasks_.every("heartbeat", heartbeat_interval, [this]() { heartbeat(); });
136 tasks_.every("rules_reload", rules_interval, [this]() { reload_rules(); });
137 tasks_.every("firewall_reload", firewall_interval, [this]() { reload_firewall(); });
138
139 const auto proc_method = [&]() {
140 auto method = config_.proc_monitor_method;
141 for (auto& ch : method) ch = static_cast<char>(std::tolower(static_cast<unsigned char>(ch)));
142 return method;
143 }();
144
145 if (proc_method == "netlink" || proc_method == "proc" || proc_method == "audit" || proc_method == "ebpf") {
146 if (proc_events_.start([this](const bsc::netlink::ProcEvent& ev) { handle_proc_event(ev); })) {
147 log.info("proc event monitor attivo via netlink connector");
148 if (proc_method == "audit" || proc_method == "ebpf") {
149 // Quando l'utente sceglie "ebpf" attiviamo il monitor eBPF
150 // in aggiunta al fallback netlink/proc già avviato sopra.
151 // net_ è il SocketSnapshot condiviso: start_ebpf() carica
152 // il programma BPF e avvia il thread di draining.
153 // Se fallisce (kernel senza BTF, cap assenti) il daemon
154 // prosegue normalmente via NFQUEUE + /proc/net/tcp.
155 if (proc_method == "ebpf") {
156 const auto ebpf_obj = config_.ebpf_obj_path.empty()
157 ? std::string{"/usr/lib/bastionguard-sc/bsc_ebpf.o"}
159 if (net_.start_ebpf(ebpf_obj)) {
160 log.info("eBPF monitor attivo: tracepoint inet_sock_set_state");
161 ui_.post_alert("eBPF monitor attivo — intercettazione a livello kernel");
162 } else {
163 ui_.post_alert("eBPF non disponibile — fallback NFQUEUE+/proc");
164 }
165 } else {
166 ui_.post_alert("ProcMonitorMethod=" + config_.proc_monitor_method + " richiesto: uso netlink/proc come fallback runtime nel port C++");
167 }
168 }
169 } else {
170 log.warning("proc event monitor non disponibile, resto in modalità on-demand /proc");
171 }
172 }
173
175 // Installa la regola nft che dirotta il traffico in NFQUEUE.
176 // Se fallisce (niente nft, niente CAP_NET_ADMIN, kernel senza
177 // nf_tables) restiamo in modalita' polling /proc: gli alert partono
178 // comunque dallo scanner periodico.
179 const bool nft_ok = firewall_.install_nfqueue_rule(
181
182 if (nft_ok && nfqueue_.open(config_.fw_options.queue_num, [this](const bsc::netfilter::PacketInfo& pkt) {
183 if (!desktop_allows_activity()) {
184 bsc::netfilter::Decision d;
185 d.verdict = bsc::netfilter::Verdict::Accept;
186 return d;
187 }
188
189 auto conn = pkt.connection;
190 if (conn.user_id == 0xffffffffu && pkt.uid != 0xffffffffu) conn.user_id = pkt.uid;
191
193 if (!has_process_identity(conn)) enrich_from_recent_flows(conn);
194
195 for (int i = 0; i < 4 && !has_process_identity(conn); ++i) {
197 std::this_thread::sleep_for(std::chrono::milliseconds(15 + 10 * i));
199 if (!has_process_identity(conn)) enrich_from_recent_flows(conn);
200 }
201 if (!has_process_identity(conn)) enrich_from_recent_processes(conn, std::chrono::milliseconds(2500));
202 if (!has_process_identity(conn)) enrich_from_recent_processes(conn, std::chrono::milliseconds(6000));
203
204 return decide_connection(std::move(conn), true, false);
205 })) {
206 log.info("NFQUEUE attiva sulla coda " + std::to_string(config_.fw_options.queue_num));
207 ui_.post_alert("NFQUEUE attiva: intercettazione pacchetti in tempo reale");
208 } else {
209 if (nft_ok) firewall_.remove_nfqueue_rule(); // apertura coda fallita, rollback
210 log.warning("NFQUEUE non disponibile: uso polling /proc come fallback");
211 ui_.post_alert("NFQUEUE non disponibile: fallback su /proc snapshot");
212 }
213 }
214
215 log.info("bsc-daemon-cpp started");
216 ui_.post_alert("bsc-daemon-cpp avviato");
217 ui_.post_alert("runtime: proc_monitor_method=" + config_.proc_monitor_method + ", queue_num=" + std::to_string(config_.fw_options.queue_num));
218 while (running_) std::this_thread::sleep_for(std::chrono::milliseconds(250));
219 ui_.post_alert("bsc-daemon-cpp arrestato");
220 // Breve attesa perche' il post_alert pending possa lasciare il processo
221 // attraverso il socket.
222 std::this_thread::sleep_for(std::chrono::milliseconds(200));
223 nfqueue_.close();
224 firewall_.remove_nfqueue_rule(); // pulizia nft all'uscita
225 proc_events_.stop();
226 net_.stop_ebpf(); // detach tracepoint eBPF (no-op se non avviato)
227 tasks_.stop();
228 ui_.stop(); // chiude thread reader + socket
229 log.info("bsc-daemon-cpp stopped");
230}
231
232void DaemonService::stop() { running_ = false; }
233
235 return desktop_active_.load() && ui_.is_connected();
236}
237
239 const bool previous = desktop_active_.exchange(active);
240 if (previous == active) return;
241
242 auto& log = bsc::logging::Logger::instance();
243 if (active) {
245 {
246 std::lock_guard lock(seen_mutex_);
247 seen_.clear();
248 }
249 net_.invalidate_cache();
250 log.info("Desktop attivo: monitoraggio connessioni riattivato");
251 } else {
252 {
253 std::lock_guard lock(seen_mutex_);
254 seen_.clear();
255 }
256 net_.discard_pending_events();
257 log.info("Desktop inattivo/bloccato: sospendo scansione e popup connessioni");
258 }
259}
260
262 std::error_code ec;
263 fs::file_time_type latest{};
264 if (!fs::exists(config_.rules.path, ec)) return false;
265 for (const auto& entry : fs::directory_iterator(config_.rules.path, ec)) {
266 if (!entry.is_regular_file() || entry.path().extension() != ".json") continue;
267 latest = std::max(latest, fs::last_write_time(entry.path(), ec));
268 }
269 const bool changed = latest != rules_mtime_;
270 if (changed) const_cast<DaemonService*>(this)->rules_mtime_ = latest;
271 return changed;
272}
273
275 std::lock_guard lock(rules_reload_mutex_);
276 if (rules_changed()) {
277 const auto aliases = std::filesystem::path(config_.rules.path).parent_path() / "network_aliases.json";
278 rules_.load_aliases(aliases.string());
279 rules_.load_directory(config_.rules.path);
280 }
281}
282
284 if (config_.fw_options.config_path.empty()) {
285 return;
286 }
287
288 if (!firewall_.reload(config_.fw_options.config_path)) {
289 const std::string message =
290 std::string{_("Reload firewall fallito: ")} +
291 config_.fw_options.config_path;
292
294 ui_.post_alert(message);
295 }
296}
297
299 auto& log = bsc::logging::Logger::instance();
300 log.info("notification: " + ev.type);
301
302 // I tipi sono lowercase (mapping stringhe definito in ipc/UIServer.cpp).
303 if (ev.type == "reload_fw_rules") {
305 } else if (ev.type == "stop") {
306 stop();
307 } else if (ev.type == "enable_interception") {
308 config_.intercept_unknown = true;
309 } else if (ev.type == "disable_interception") {
310 config_.intercept_unknown = false;
311 } else if (ev.type == "desktop_active") {
312 set_desktop_active(true);
313 } else if (ev.type == "desktop_idle") {
314 set_desktop_active(false);
315 } else if (ev.type == "enable_firewall") {
316 if (!config_.fw_options.config_path.empty()) firewall_.reload(config_.fw_options.config_path);
317 tasks_.every("firewall_reload",
318 parse_interval(config_.fw_options.monitor_interval, std::chrono::seconds(15)),
319 [this]() { reload_firewall(); });
320 } else if (ev.type == "disable_firewall") {
321 log.warning("disable firewall: sospendo i reload periodici");
322 tasks_.stop_task("firewall_reload");
323 } else if (ev.type == "task_stop") {
324 try {
325 auto j = json::parse(ev.data);
326 const auto name = j.value("Name", std::string{});
327 if (!name.empty()) tasks_.stop_task(name);
328 } catch (...) { log.warning("task_stop con JSON non valido"); }
329 } else if (ev.type == "task_start") {
330 try {
331 auto j = json::parse(ev.data);
332 const auto name = j.value("Name", std::string{});
333 const auto interval = parse_interval(j.value("Interval", std::string{"5s"}), std::chrono::seconds(5));
334 if (name == "scan") tasks_.every("scan", interval, [this]() { scan_connections(); });
335 else if (name == "heartbeat") tasks_.every("heartbeat", interval, [this]() { heartbeat(); });
336 else if (name == "rules_reload") tasks_.every("rules_reload", interval, [this]() { reload_rules(); });
337 else if (name == "firewall_reload") tasks_.every("firewall_reload", interval, [this]() { reload_firewall(); });
338 } catch (...) { log.warning("task_start con JSON non valido"); }
339 } else if (ev.type == "log_level") {
340 try {
341 auto lvl = std::stoi(ev.data);
342 config_.log_level = lvl <= 0 ? 0 : lvl >= 3 ? 3 : lvl;
344 } catch (...) { log.warning("log_level non valido"); }
345 } else if (ev.type == "change_rule" || ev.type == "enable_rule" || ev.type == "disable_rule") {
346 for (auto r : ev.rules) {
347 if (ev.type == "disable_rule") r.enabled = false;
348 if (ev.type == "enable_rule") r.enabled = true;
349 rules_.save_rule(r, config_.rules.path);
350 }
351 reload_rules();
352 } else if (ev.type == "delete_rule") {
353 for (const auto& r : ev.rules) {
354 std::error_code ec;
355 fs::remove(fs::path(config_.rules.path) / (r.name + ".json"), ec);
356 }
357 reload_rules();
358 } else if (ev.type == "change_config") {
359 try {
360 auto j = json::parse(ev.data);
361 if (j.contains("DefaultAction")) config_.default_action = j["DefaultAction"].get<std::string>();
362 if (j.contains("DefaultDuration")) config_.default_duration = j["DefaultDuration"].get<std::string>();
363 if (j.contains("Firewall")) config_.firewall = j["Firewall"].get<std::string>();
364 if (j.contains("ProcMonitorMethod")) config_.proc_monitor_method = j["ProcMonitorMethod"].get<std::string>();
365 if (j.contains("EBPFObjPath")) config_.ebpf_obj_path = j["EBPFObjPath"].get<std::string>();
366 if (j.contains("InterceptUnknown")) config_.intercept_unknown = j["InterceptUnknown"].get<bool>();
367 if (j.contains("LogUTC")) config_.log_utc = j["LogUTC"].get<bool>();
368 if (j.contains("LogMicro")) config_.log_micro = j["LogMicro"].get<bool>();
369 if (j.contains("LogLevel")) {
370 config_.log_level = j["LogLevel"].get<int>();
372 config_.log_level <= 0 ? 0 : config_.log_level >= 3 ? 3 : config_.log_level));
373 }
376 const auto target = config_.source_path.empty()
377 ? std::string{"/etc/bastionguard-secure-connectiond/default-config.json"}
378 : config_.source_path;
379 (void)bsc::config::save_file(config_, target);
380 } catch (...) { log.warning("change_config con JSON non valido"); }
381 }
382}
383
385 auto& log = bsc::logging::Logger::instance();
386 const auto pid = ev.tgid ? ev.tgid : ev.pid;
388 proc_.note_fork(pid, ev.parent_tgid ? ev.parent_tgid : ev.parent_pid);
389 log.debug("fork pid=" + std::to_string(pid) + " ppid=" + std::to_string(ev.parent_tgid ? ev.parent_tgid : ev.parent_pid));
390 } else if (ev.type == bsc::netlink::ProcEvent::Type::Exec) {
391 proc_.note_exec(pid, config_.rules.enable_checksums);
392 if (auto p = proc_.cached(pid, false)) {
393 log.debug("exec pid=" + std::to_string(p->pid) + " path=" + p->path);
394 }
395 } else if (ev.type == bsc::netlink::ProcEvent::Type::Exit) {
396 proc_.note_exit(pid);
397 log.debug("exit pid=" + std::to_string(pid) + " code=" + std::to_string(ev.exit_code));
398 }
399}
400
402 if (decision.duration != "always") return;
403 bsc::rule::Rule persisted = decision;
404 persisted.created = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count();
405 if (persisted.name.empty()) {
406 std::ostringstream name;
407 name << "autogen-" << persisted.action << '-' << conn.process_id << '-' << conn.dst_port << '-' << std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count();
408 persisted.name = name.str();
409 }
410 persisted.description = "Auto-generated from UI decision";
411 persisted.enabled = true;
412 persisted.precedence = false;
413 persisted.nolog = false;
414
415 bsc::rule::Operator proc_op;
417 if (!conn.process_path.empty()) {
418 proc_op.operand = "process.path";
419 proc_op.data = conn.process_path;
420 } else if (!conn.process_comm.empty()) {
421 proc_op.operand = "process.name";
422 proc_op.data = conn.process_comm;
423 } else {
424 proc_op.operand = "process.id";
425 proc_op.data = std::to_string(conn.process_id);
426 }
427 bsc::rule::Operator host_op;
429 host_op.operand = !conn.dst_host.empty() ? "dest.host" : "dest.ip";
430 host_op.data = !conn.dst_host.empty() ? conn.dst_host : conn.dst_ip;
431 bsc::rule::Operator port_op;
433 port_op.operand = "dest.port";
434 port_op.data = std::to_string(conn.dst_port);
436 persisted.op.operand = "list";
437 persisted.op.list = {proc_op, host_op, port_op};
438
439 if (rules_.save_rule(persisted, config_.rules.path)) {
440 reload_rules();
441 ui_.post_alert("Regola persistente salvata: " + persisted.name);
442 }
443}
444
446 if (conn.process_id == 0 && conn.process_path.empty() && conn.user_id == 0xffffffffu) return;
447 const auto now = std::chrono::steady_clock::now();
448 std::lock_guard lock(flow_mutex_);
449 recent_flows_[conn.key()] = {conn, now};
450 if (recent_flows_.size() > 4096) {
451 for (auto it = recent_flows_.begin(); it != recent_flows_.end();) {
452 if (now - it->second.second > kRecentFlowTtl) it = recent_flows_.erase(it);
453 else ++it;
454 }
455 }
456}
457
459 const auto now = std::chrono::steady_clock::now();
460 std::lock_guard lock(flow_mutex_);
461 for (auto it = recent_flows_.begin(); it != recent_flows_.end();) {
462 if (now - it->second.second > kRecentFlowTtl) {
463 it = recent_flows_.erase(it);
464 continue;
465 }
466 const auto& cached = it->second.first;
467 const bool same_direct = conn.protocol == cached.protocol && conn.src_ip == cached.src_ip && conn.src_port == cached.src_port && conn.dst_ip == cached.dst_ip && conn.dst_port == cached.dst_port;
468 const bool same_reverse = conn.protocol == cached.protocol && conn.src_ip == cached.dst_ip && conn.src_port == cached.dst_port && conn.dst_ip == cached.src_ip && conn.dst_port == cached.src_port;
469 if (same_direct || same_reverse) {
470 if (conn.process_id == 0) conn.process_id = cached.process_id;
471 if (conn.user_id == 0xffffffffu) conn.user_id = cached.user_id;
472 if (conn.process_path.empty()) conn.process_path = cached.process_path;
473 if (conn.process_comm.empty() || looks_like_browser_thread_comm(conn.process_comm)) conn.process_comm = cached.process_comm;
474 if (conn.process_cwd.empty()) conn.process_cwd = cached.process_cwd;
475 if (conn.process_args.empty()) conn.process_args = cached.process_args;
476 if (conn.process_env.empty()) conn.process_env = cached.process_env;
477 if (conn.process_checksums.empty()) conn.process_checksums = cached.process_checksums;
478 if (conn.process_tree.empty()) conn.process_tree = cached.process_tree;
479 return;
480 }
481 ++it;
482 }
483}
484
485void DaemonService::enrich_from_recent_processes(bsc::model::Connection& conn, std::chrono::milliseconds max_age) {
486 if (conn.process_id != 0 && !conn.process_comm.empty() && !conn.process_path.empty()) return;
487
488 std::vector<bsc::model::ProcessInfo> candidates;
489 if (conn.user_id != 0xffffffffu) candidates = proc_.recent_candidates(conn.user_id, max_age, 8);
490 else candidates = proc_.recent_candidates_any(max_age, 4);
491 if (candidates.empty()) return;
492
493 const auto& best = candidates.front();
494 bool unambiguous = candidates.size() == 1;
495 if (!unambiguous) {
496 unambiguous = true;
497 for (std::size_t i = 1; i < std::min<std::size_t>(candidates.size(), 3); ++i) {
498 if (!same_process_signature(best, candidates[i])) {
499 unambiguous = false;
500 break;
501 }
502 }
503 }
504 if (!unambiguous) return;
505 merge_process_info(conn, best);
506}
507
509 const std::string host = !c.dst_host.empty() ? c.dst_host : c.dst_ip;
510 std::string proc_key = !c.process_path.empty() ? c.process_path : c.process_comm;
511 if (proc_key.empty() && c.process_id != 0) proc_key = "pid:" + std::to_string(c.process_id);
512 return proc_key + "|" + host + "|" + std::to_string(c.dst_port);
513}
514
515std::optional<DaemonService::CachedVerdict>
517 const auto key = verdict_key(c);
518 const auto now = std::chrono::steady_clock::now();
519 std::lock_guard lk(verdict_cache_mutex_);
520 auto it = verdict_cache_.find(key);
521 if (it == verdict_cache_.end()) return std::nullopt;
522 if (it->second.expires_at != std::chrono::steady_clock::time_point{} &&
523 now >= it->second.expires_at) {
524 return std::nullopt; // scaduto
525 }
526 return it->second;
527}
528
530 // Memorizziamo SOLO verdetti "always": once e' per definizione one-shot.
531 // Per "always" teniamo scadenza infinita (salvato anche su disco come
532 // regola — vedi persist_learned_rule).
533 if (r.duration != "always") return;
534 CachedVerdict cv{r.action, r.duration, std::chrono::steady_clock::time_point{}};
535 std::lock_guard lk(verdict_cache_mutex_);
536 verdict_cache_[verdict_key(c)] = std::move(cv);
537 // Bound di sicurezza: dimentica il piu' vecchio se superiamo 4096 entry.
538 if (verdict_cache_.size() > 4096) {
539 auto oldest = verdict_cache_.begin();
540 verdict_cache_.erase(oldest);
541 }
542}
543
545 auto& log = bsc::logging::Logger::instance();
546
547 // Se il desktop non è attivo o la UI non è connessa, non fare lookup,
548 // reverse DNS, popup o scansioni costose: lascia passare e basta.
549 // Questo evita allarmi/attività di rete quando la sessione grafica è
550 // inattiva, bloccata o non ancora pronta.
554 return d;
555 }
556
557 if (conn.process_id == 0 || conn.process_path.empty()) enrich_from_recent_flows(conn);
558 if (!has_process_identity(conn)) {
559 enrich_from_recent_processes(conn, std::chrono::milliseconds(2500));
560 }
561 if (!has_process_identity(conn)) {
562 enrich_from_recent_processes(conn, std::chrono::milliseconds(6000));
563 }
564 if (conn.process_id > 0) {
565 if (auto p = proc_.inspect(conn.process_id, config_.rules.enable_checksums)) {
566 merge_process_info(conn, *p);
567 }
568 }
569 if (realtime && conn.process_id == 0 && conn.process_comm.empty() && conn.process_path.empty()) {
570 log.warning("Connessione senza processo risolto: uid=" + std::to_string(conn.user_id) +
571 " dst=" + conn.dst_ip + ":" + std::to_string(conn.dst_port));
572 }
573 if (conn.dst_host.empty()) {
574 conn.dst_host = dns_.reverse_lookup(conn.dst_ip);
575 if (!conn.dst_host.empty()) stats_.note_dns_response();
576 }
577
578 std::string action;
579 std::string rule_name;
580 std::string duration = config_.default_duration;
581 bool asked_ui = false;
582
583 // 1) Cache dei verdetti "always" dati dall'utente in questa sessione:
584 // se Firefox ha gia' avuto un "allow always" verso google.com:443,
585 // le prossime 50 connessioni non aprono popup.
586 if (auto cached = verdict_lookup(conn)) {
587 action = cached->action;
588 duration = cached->duration;
589 rule_name = "cache";
590 }
591 // 2) Regole JSON persistite (localhost, regole utente salvate)
592 else if (auto matched = rules_.find_match(conn)) {
593 action = matched->action;
594 duration = matched->duration;
595 rule_name = matched->name;
596 }
597 // 3) Chiedi all'utente solo fuori dal callback NFQUEUE.
598 // Il callback NFQUEUE deve restare veloce: se aspetta il popup per
599 // 15-30s, la coda kernel si riempie e gli eventi successivi sembrano
600 // "persi". NFQUEUE quindi applica cache/regole/default e la scansione
601 // periodica farà l'eventuale AskRule senza bloccare la coda pacchetti.
602 else if (config_.intercept_unknown && realtime && allow_blocking_ask) {
603 if (is_asking_.load()) {
604 log.debug("ask_rule gia' in volo, applico default_action=" +
605 config_.default_action + " per " + conn.process_path);
606 action = config_.default_action;
607 } else {
608 is_asking_ = true;
609 asked_ui = true;
610 log.info("AskRule: " + conn.protocol + " " + conn.process_path + " -> " +
611 (!conn.dst_host.empty() ? conn.dst_host : conn.dst_ip) +
612 ":" + std::to_string(conn.dst_port));
613 auto asked = ui_.ask_rule(conn);
614 is_asking_ = false;
615
616 if (asked) {
617 action = asked->action.empty() ? config_.default_action : asked->action;
618 duration = asked->duration.empty() ? config_.default_duration : asked->duration;
619 rule_name = asked->name;
620 asked->duration = duration;
621 asked->action = action;
622 log.info("AskRule risposta: action=" + action + ", duration=" + duration);
623 verdict_store(conn, *asked); // cache in-memory
624 persist_learned_rule(conn, *asked); // + eventuale save su disco
625 } else {
626 log.warning("AskRule senza risposta, uso default_action=" +
627 config_.default_action);
628 action = config_.default_action;
629 }
630 }
631 } else {
632 action = config_.default_action;
633 }
634
636 stats_.record(conn, action, rule_name);
637
638 // Non perdere più le connessioni "default": se la UI non ha già ricevuto
639 // un AskRule per questa connessione, invia sempre l'evento realtime.
640 // La UI aggiorna liste/mappa/statistiche senza dover aprire un popup.
641 if (realtime && !asked_ui && desktop_allows_activity()) {
642 log.info("PostAlert UI: action=" + action +
643 (rule_name.empty() ? std::string{} : ", rule=" + rule_name));
644 ui_.post_connection_alert(conn, action, rule_name);
645 }
646
647 std::ostringstream oss;
648 oss << conn.protocol << ' ' << conn.process_path << ' ' << conn.dst_ip << ':' << conn.dst_port << " -> " << action;
649 if (!rule_name.empty()) oss << " [" << rule_name << ']';
650 log.info(oss.str());
651
654 return d;
655}
656
659 if (!inactive_notice_logged_.exchange(true)) {
661 "scan sospesa: desktop inattivo o UI non connessa");
662 }
663 net_.discard_pending_events();
664 return;
665 }
667
668 for (auto conn : net_.list_connections()) {
669 if ((conn.dst_ip == "0.0.0.0" || conn.dst_ip == "::") && conn.dst_port == 0) continue;
670 const auto key = conn.key();
671 {
672 std::lock_guard lock(seen_mutex_);
673 if (seen_.contains(key)) continue;
674 seen_.insert(key);
675 if (seen_.size() > 50000) seen_.clear();
676 }
677 (void)decide_connection(std::move(conn), config_.intercept_unknown, true);
678 }
679}
680
682 ui_.ping(stats_);
683}
684
685} // namespace bsc::service
#define _(s)
bool install_nfqueue_rule(std::uint16_t queue_num, bool queue_bypass=true) const
Definition Firewall.cpp:472
void remove_nfqueue_rule() const
Definition Firewall.cpp:583
bool reload(const std::string &config_path) const
Definition Firewall.cpp:394
void warning(const std::string &msg)
Definition Logger.cpp:22
void set_microseconds(bool enabled)
Definition Logger.cpp:18
void info(const std::string &msg)
Definition Logger.cpp:21
static Logger & instance()
Definition Logger.cpp:11
void set_utc(bool enabled)
Definition Logger.cpp:17
void set_level(Level level)
Definition Logger.cpp:16
bool enrich_connection(bsc::model::Connection &conn) const
bool start_ebpf(const std::string &obj_path="/usr/lib/bastionguard-sc/bsc_ebpf.o")
bool open(std::uint16_t queue_id, Handler handler)
Definition NFQueue.cpp:249
void load_aliases(const std::string &path)
Definition Rule.cpp:292
void load_directory(const std::string &dir)
Definition Rule.cpp:311
static std::chrono::milliseconds parse_interval(const std::string &v, std::chrono::milliseconds def=std::chrono::seconds(5))
bsc::stats::Statistics stats_
bsc::netlink::ProcEventMonitor proc_events_
void enrich_from_recent_processes(bsc::model::Connection &conn, std::chrono::milliseconds max_age=std::chrono::milliseconds(5000))
bsc::netfilter::Decision decide_connection(bsc::model::Connection conn, bool realtime, bool allow_blocking_ask=true)
static std::string verdict_key(const bsc::model::Connection &c)
void verdict_store(const bsc::model::Connection &c, const bsc::rule::Rule &r)
void set_desktop_active(bool active)
bsc::net::SocketSnapshot net_
bsc::net::DnsResolver dns_
void handle_notification(const bsc::ui::NotificationEvent &ev)
bsc::config::Config config_
std::unordered_map< std::string, CachedVerdict > verdict_cache_
bsc::task::TaskRunner tasks_
DaemonService(bsc::config::Config config)
bsc::netfilter::NFQueue nfqueue_
void remember_connection(const bsc::model::Connection &conn)
void handle_proc_event(const bsc::netlink::ProcEvent &ev)
std::filesystem::file_time_type rules_mtime_
std::set< std::string > seen_
std::atomic_bool desktop_active_
std::optional< CachedVerdict > verdict_lookup(const bsc::model::Connection &c) const
std::atomic_bool inactive_notice_logged_
void persist_learned_rule(const bsc::model::Connection &conn, const bsc::rule::Rule &decision)
std::unordered_map< std::string, std::pair< bsc::model::Connection, std::chrono::steady_clock::time_point > > recent_flows_
void enrich_from_recent_flows(bsc::model::Connection &conn)
void every(const std::string &name, std::chrono::milliseconds interval, std::function< void()> fn)
Definition TaskRunner.cpp:9
bool post_alert(const std::string &text)
Definition UIClient.cpp:456
void set_notification_handler(std::function< void(const NotificationEvent &)> cb)
Definition UIClient.cpp:384
bool save_file(const Config &cfg, const std::string &path)
Definition Config.cpp:104
bool available()
Definition NFQueue.cpp:251
void merge_process_info(bsc::model::Connection &conn, const bsc::model::ProcessInfo &p)
bool has_process_identity(const bsc::model::Connection &c)
bool looks_like_browser_thread_comm(const std::string &comm)
bool same_process_signature(const bsc::model::ProcessInfo &a, const bsc::model::ProcessInfo &b)
nlohmann::json json
RulesOptions rules
Definition Config.hpp:58
std::string proc_monitor_method
Definition Config.hpp:54
std::string ebpf_obj_path
Definition Config.hpp:55
FwOptions fw_options
Definition Config.hpp:56
TasksOptions tasks
Definition Config.hpp:61
ServerConfig server
Definition Config.hpp:57
std::vector< std::pair< std::string, std::uint32_t > > process_tree
std::uint32_t process_id
std::vector< std::string > process_args
std::uint32_t dst_port
std::uint32_t src_port
std::map< std::string, std::string > process_checksums
std::map< std::string, std::string > process_env
std::string key() const
Definition Connection.cpp:6
std::uint32_t user_id
std::map< std::string, std::string > env
std::map< std::string, std::string > checksums
std::vector< std::pair< std::string, std::uint32_t > > process_tree
std::vector< std::string > args
bsc::model::Connection connection
Definition NFQueue.hpp:26
std::string operand
Definition Rule.hpp:15
std::string data
Definition Rule.hpp:16
OperatorType type
Definition Rule.hpp:14
std::vector< Operator > list
Definition Rule.hpp:18
bool precedence
Definition Rule.hpp:28
std::string name
Definition Rule.hpp:25
std::string duration
Definition Rule.hpp:31
Operator op
Definition Rule.hpp:32
std::string action
Definition Rule.hpp:30
std::string description
Definition Rule.hpp:26
std::int64_t created
Definition Rule.hpp:24
std::vector< bsc::rule::Rule > rules
Definition UIClient.hpp:25