Skip to content
API Documentation
BastionGuard™
EBPFMon.cpp
Go to the documentation of this file.
1/*
2 * BastionGuard™ Secure Connection — EBPFMon implementazione
3 * Copyright (C) 2025–2026 Calogero Scarnà
4 * GPL-3.0
5 *
6 * Se BSC_HAS_EBPF non e' definito (libbpf assente a compile-time)
7 * tutte le funzioni sono stub vuoti — zero overhead sul daemon.
8 *
9 * Cambiamenti v2 (copertura estesa):
10 * - attacchiamo tutti i programmi dentro il .o, non solo il tracepoint
11 * `bsc_inet_sock_set_state`. Ora coesistono anche i kprobe per
12 * tcp_v{4,6}_connect e udp{,v6}_sendmsg.
13 * - se un singolo attach fallisce (es. simbolo non presente su un
14 * kernel custom) non abortiamo tutto: loggiamo warning e proseguiamo.
15 * Il daemon cade elegantemente da "coverage completa" a "coverage
16 * parziale" invece di disabilitare eBPF del tutto.
17 */
18
19#include "bsc/ebpf/EBPFMon.hpp"
21#include <arpa/inet.h>
22#include <chrono>
23#include <cstring>
24
25#ifdef BSC_HAS_EBPF
26# include <bpf/libbpf.h>
27# include <bpf/bpf.h>
28#endif
29
30namespace bsc::ebpf {
31
32/* ── Conversione evento → Connection ──────────────────────────── */
33
35{
37
38 c.process_id = ev.pid;
39 c.user_id = ev.uid;
40 c.process_comm = std::string(ev.comm,
41 strnlen(ev.comm, sizeof(ev.comm)));
42
43 c.protocol = (ev.proto == 6) ? "tcp" : "udp";
44
45 char buf[INET6_ADDRSTRLEN]{};
46 if (ev.family == AF_INET) {
47 // src
48 inet_ntop(AF_INET, ev.src_addr, buf, sizeof(buf));
49 c.src_ip = buf;
50 // dst
51 inet_ntop(AF_INET, ev.dst_addr, buf, sizeof(buf));
52 c.dst_ip = buf;
53 } else {
54 inet_ntop(AF_INET6, ev.src_addr, buf, sizeof(buf));
55 c.src_ip = buf;
56 inet_ntop(AF_INET6, ev.dst_addr, buf, sizeof(buf));
57 c.dst_ip = buf;
58 }
59
60 c.src_port = ev.src_port;
61 c.dst_port = ev.dst_port;
62
63 // Timestamp: converte ns monotoni in system_clock (approssimato)
64 // Il daemon usa seen_at solo per ordinamento nella UI — non critico.
65 c.seen_at = std::chrono::system_clock::now();
66
67 return c;
68}
69
70/* ── EBPFMon ──────────────────────────────────────────────────── */
71
72EBPFMon::EBPFMon() = default;
74
75#ifdef BSC_HAS_EBPF
76
77// Callback chiamata da ring_buffer__poll per ogni record nella ring buffer.
78// Converte il byte blob in ConnEvent e lo accoda nel buffer del monitor.
79// Callback C per libbpf ring_buffer__new.
80// Dichiarata friend in EBPFMon.hpp per accedere ai membri privati.
81int ringbuf_callback(void* ctx, void* data, size_t size)
82{
83 if (!ctx || !data) return 0;
84
85 // Retro-compat: accettiamo sia il layout v1 (sizeof ConnEvent v1 = stesso
86 // di v2 perche' abbiamo sostituito _pad[2] con event_kind+_pad[1], quindi
87 // e' letteralmente identico). Qui serve solo che il record sia >= quello
88 // che copiamo — se un giorno estendiamo oltre, il kernel potrebbe ancora
89 // scrivere record piu' piccoli: in quel caso ignoriamo gracefully.
90 if (size < sizeof(ConnEvent)) return 0;
91
92 auto* mon = static_cast<EBPFMon*>(ctx);
93 ConnEvent ev{};
94 std::memcpy(&ev, data, sizeof(ev));
95
96 // Non filtrare il loopback qui: anche le connessioni locali servono per
97 // attribuire correttamente il processo ai popup realtime. Le regole UI o
98 // firewall possono decidere di ignorarle piu' avanti, ma il proc attribution
99 // deve comunque essere catturato e messo in cache.
100
101 auto conn = event_to_connection(ev);
102 {
103 std::lock_guard lk(mon->buf_mutex_);
104 mon->buf_.push_back(std::move(conn));
105
106 // Tetto di sicurezza: non accumuliamo piu' di 8192 eventi non ancora
107 // consumati da drain(). Se il daemon e' lento (debug build, alto carico)
108 // droppare eventi eBPF e' preferibile a OOM. Il fallback /proc/net/tcp
109 // copre i gap.
110 if (mon->buf_.size() > 8192)
111 mon->buf_.erase(mon->buf_.begin(),
112 mon->buf_.begin() + (mon->buf_.size() - 8192));
113 }
114 return 0;
115}
116
117bool EBPFMon::start(const std::string& obj_path)
118{
119 auto& log = bsc::logging::Logger::instance();
120
121 // ── 1. Silenzia i messaggi di libbpf verso stderr ──────────────
122 libbpf_set_strict_mode(LIBBPF_STRICT_ALL);
123 // Reindirizza i log di libbpf al nostro logger
124 libbpf_set_print([](enum libbpf_print_level level,
125 const char* fmt, va_list args) -> int {
126 char buf[512];
127 vsnprintf(buf, sizeof(buf), fmt, args);
129 if (level == LIBBPF_WARN) l.warning(std::string("[libbpf] ") + buf);
130 else if (level == LIBBPF_INFO) l.info(std::string("[libbpf] ") + buf);
131 else l.debug(std::string("[libbpf] ") + buf);
132 return 0;
133 });
134
135 // ── 2. Carica l'oggetto BPF ────────────────────────────────────
136 struct bpf_object_open_opts opts{};
137 opts.sz = sizeof(opts);
138 struct bpf_object* obj = bpf_object__open_file(obj_path.c_str(), &opts);
139 if (!obj || libbpf_get_error(obj)) {
140 log.warning("EBPFMon: impossibile aprire " + obj_path +
141 " — eBPF disabilitato");
142 return false;
143 }
144 bpf_obj_ = obj;
145
146 if (bpf_object__load(obj) != 0) {
147 log.warning("EBPFMon: bpf_object__load fallito — "
148 "kernel BTF disponibile? CAP_BPF presente?");
149 bpf_object__close(obj);
150 bpf_obj_ = nullptr;
151 return false;
152 }
153
154 // ── 3. Attach di TUTTI i programmi dell'oggetto ────────────────
155 //
156 // Iteriamo su ogni bpf_program definito nel .o e facciamo attach.
157 // bpf_program__attach() legge la sezione SEC() del programma e
158 // usa il tipo appropriato (tracepoint, kprobe, ...).
159 //
160 // Se un singolo attach fallisce (esempio classico: kprobe su un
161 // simbolo che non esiste nel kernel in esecuzione) non abortiamo
162 // l'intera init: loggiamo e proseguiamo, cosi' la coverage resta
163 // parziale invece di essere nulla.
164 int attached = 0;
165 int total = 0;
166 struct bpf_program* prog = nullptr;
167 bpf_object__for_each_program(prog, obj) {
168 ++total;
169 const char* name = bpf_program__name(prog);
170 const char* sect = bpf_program__section_name(prog);
171
172 struct bpf_link* lnk = bpf_program__attach(prog);
173 if (!lnk || libbpf_get_error(lnk)) {
174 log.warning(std::string("EBPFMon: attach fallito per ")
175 + (name ? name : "?")
176 + " (sezione " + (sect ? sect : "?") + ") — "
177 + "il simbolo potrebbe non esistere su questo kernel");
178 continue;
179 }
180 links_.push_back(lnk);
181 ++attached;
182 log.debug(std::string("EBPFMon: attached ")
183 + (name ? name : "?")
184 + " (" + (sect ? sect : "?") + ")");
185 }
186
187 if (attached == 0) {
188 log.warning("EBPFMon: nessun programma attaccato — disabilito eBPF");
189 bpf_object__close(obj);
190 bpf_obj_ = nullptr;
191 return false;
192 }
193
194 // ── 4. Apri la ring buffer ─────────────────────────────────────
195 struct bpf_map* rb_map =
196 bpf_object__find_map_by_name(obj, "bsc_events");
197 if (!rb_map) {
198 log.warning("EBPFMon: mappa 'bsc_events' non trovata");
199 for (auto* l : links_) bpf_link__destroy(static_cast<struct bpf_link*>(l));
200 links_.clear();
201 bpf_object__close(obj);
202 bpf_obj_ = nullptr;
203 return false;
204 }
205
206 struct ring_buffer* rb =
207 ring_buffer__new(bpf_map__fd(rb_map), ringbuf_callback, this, nullptr);
208 if (!rb) {
209 log.warning("EBPFMon: ring_buffer__new fallito");
210 for (auto* l : links_) bpf_link__destroy(static_cast<struct bpf_link*>(l));
211 links_.clear();
212 bpf_object__close(obj);
213 bpf_obj_ = nullptr;
214 return false;
215 }
216 ring_buf_ = rb;
217
218 // ── 5. Avvia il thread di polling ─────────────────────────────
219 stop_.store(false);
220 active_.store(true);
221 thread_ = std::thread(&EBPFMon::drain_loop, this);
222
223 log.info("EBPFMon: attivo — " + std::to_string(attached) + "/" +
224 std::to_string(total) + " probe agganciati "
225 "(tracepoint + kprobes TCP/UDP)");
226 return true;
227}
228
229void EBPFMon::stop()
230{
231 if (!active_.load()) return;
232 stop_.store(true);
233 if (thread_.joinable()) thread_.join();
234 active_.store(false);
235
236 if (ring_buf_) {
237 ring_buffer__free(static_cast<struct ring_buffer*>(ring_buf_));
238 ring_buf_ = nullptr;
239 }
240 for (auto* l : links_) {
241 if (l) bpf_link__destroy(static_cast<struct bpf_link*>(l));
242 }
243 links_.clear();
244 if (bpf_obj_) {
245 bpf_object__close(static_cast<struct bpf_object*>(bpf_obj_));
246 bpf_obj_ = nullptr;
247 }
248 bsc::logging::Logger::instance().info("EBPFMon: fermato");
249}
250
252{
253 // ring_buffer__poll: blocca al massimo timeout_ms millisecondi,
254 // poi richiama il callback per ogni record disponibile.
255 // Con timeout 20ms abbiamo latenza ≤20ms tra evento kernel e
256 // arrivo in drain(). NFQUEUE e' gia' piu' veloce per i pacchetti
257 // intercettati; eBPF serve per i flow che NFQUEUE non vede
258 // (gia' stabiliti al boot, UDP senza SYN, ecc.)
259 auto* rb = static_cast<struct ring_buffer*>(ring_buf_);
260 while (!stop_.load()) {
261 ring_buffer__poll(rb, 20 /* ms */);
262 }
263}
264
265std::vector<bsc::model::Connection> EBPFMon::drain()
266{
267 std::vector<bsc::model::Connection> out;
268 {
269 std::lock_guard lk(buf_mutex_);
270 out.swap(buf_); // O(1), nessuna copia
271 }
272 return out;
273}
274
275#else // BSC_HAS_EBPF non definito → stub no-op
276
277bool EBPFMon::start(const std::string&)
278{
280 "EBPFMon: compilato senza libbpf — eBPF non disponibile");
281 return false;
282}
283
285
286std::vector<bsc::model::Connection> EBPFMon::drain() { return {}; }
287
289
290#endif // BSC_HAS_EBPF
291
292} // namespace bsc::ebpf
#define AF_INET
Definition bsc_ebpf.c:56
#define AF_INET6
Definition bsc_ebpf.c:57
std::atomic_bool active_
Definition EBPFMon.hpp:120
std::thread thread_
Definition EBPFMon.hpp:122
friend int ringbuf_callback(void *ctx, void *data, size_t size)
std::vector< bsc::model::Connection > drain()
Definition EBPFMon.cpp:286
bool start(const std::string &obj_path="/usr/lib/bastionguard-sc/bsc_ebpf.o")
Definition EBPFMon.cpp:277
std::atomic_bool stop_
Definition EBPFMon.hpp:121
std::vector< void * > links_
Definition EBPFMon.hpp:134
std::mutex buf_mutex_
Definition EBPFMon.hpp:124
std::vector< bsc::model::Connection > buf_
Definition EBPFMon.hpp:125
void info(const std::string &msg)
Definition Logger.cpp:21
static Logger & instance()
Definition Logger.cpp:11
bsc::model::Connection event_to_connection(const ConnEvent &ev)
Definition EBPFMon.cpp:34
uint8_t dst_addr[16]
Definition EBPFMon.hpp:64
uint8_t src_addr[16]
Definition EBPFMon.hpp:63
std::uint32_t process_id
std::chrono::system_clock::time_point seen_at
std::uint32_t dst_port
std::uint32_t src_port
std::uint32_t user_id