Skip to content
API Documentation
BastionGuard™
bsc_ebpf.c
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-only
2/*
3 * BastionGuard™ Secure Connection — eBPF probe kernel-side (esteso)
4 * Copyright (C) 2025–2026 Calogero Scarnà
5 *
6 * COPERTURA EVENTI
7 * ================
8 * Il programma aggancia tre punti nel kernel per catturare praticamente
9 * tutte le connessioni uscenti di ogni protocollo supportato:
10 *
11 * 1) tracepoint/sock/inet_sock_set_state → transizioni TCP a ESTABLISHED.
12 * Copre sia connessioni attive (dopo connect()) sia passive (dopo
13 * accept()). Avevamo gia' questo: resta per retro-compatibilita' e
14 * perche' e' il punto piu' "pulito" per attribuire il processo
15 * di un socket appena handshake-ato.
16 *
17 * 2) kprobe/tcp_v4_connect + kprobe/tcp_v6_connect
18 * Cattura ogni tentativo di connect() TCP — anche quelli che falliscono
19 * per network unreachable, timeout SYN, ecc. Era il gap principale:
20 * prima vedevamo solo le connessioni che si stabilivano correttamente.
21 *
22 * 3) kprobe/udp_sendmsg + kprobe/udpv6_sendmsg
23 * Cattura ogni datagramma UDP in uscita con destinazione esplicita.
24 * Il tracepoint inet_sock_set_state NON copre UDP senza connect(),
25 * e la stragrande maggioranza del traffico UDP (DNS, QUIC, mDNS,
26 * NTP, DHCP...) usa sendto()/sendmsg() diretti.
27 *
28 * DEDUPLICAZIONE UDP
29 * ==================
30 * Senza dedup, un client QUIC spara migliaia di sendmsg() al secondo e
31 * la ring buffer si riempirebbe di eventi ripetuti. Usiamo una LRU hash
32 * map (BPF_MAP_TYPE_LRU_HASH, 8192 entry) con chiave <pid, dst_ip, dst_port>
33 * e valore timestamp ns: emettiamo un evento solo se non ne abbiamo emesso
34 * uno identico negli ultimi UDP_DEDUP_WINDOW_NS nanosecondi (default 5s).
35 * La LRU evita la fuga di memoria: entry vecchie vengono sfrattate in
36 * automatico.
37 *
38 * Lo stesso meccanismo (con finestra piu' corta) si applica a TCP connect
39 * per gestire il retry: Linux riprova SYN 3-5 volte, non ci interessa
40 * vedere 5 righe "Firefox connect a 1.2.3.4:443".
41 *
42 * REQUISITI
43 * =========
44 * kernel >= 5.8 (bpf_ringbuf_*, LRU_HASH, fentry a volonta')
45 * BTF abilitato (CONFIG_DEBUG_INFO_BTF=y)
46 * CAP_BPF + CAP_PERFMON (o CAP_SYS_ADMIN in fallback)
47 */
48
49#include "vmlinux.h"
50#include <bpf/bpf_helpers.h>
51#include <bpf/bpf_tracing.h>
52#include <bpf/bpf_core_read.h>
53#include <bpf/bpf_endian.h>
54
55/* ── Costanti kernel ──────────────────────────────────────────────── */
56#define AF_INET 2
57#define AF_INET6 10
58#define TCP_ESTABLISHED 1
59
60#define IPPROTO_TCP 6
61#define IPPROTO_UDP 17
62
63/* Dimensione ring buffer: 8 MB — piu' grande dell'originale perche'
64 * adesso copriamo anche UDP/connect e il volume totale cresce. */
65#define RINGBUF_SIZE (8 * 1024 * 1024)
66
67/* Finestra dedup in nanosecondi. */
68#define UDP_DEDUP_WINDOW_NS (5ULL * 1000ULL * 1000ULL * 1000ULL) /* 5 s */
69#define TCP_DEDUP_WINDOW_NS (2ULL * 1000ULL * 1000ULL * 1000ULL) /* 2 s */
70
71/* ── Struttura evento condivisa con userspace ─────────────────────── */
72/*
73 * ATTENZIONE: deve combaciare byte-per-byte con bsc::ebpf::ConnEvent
74 * in daemon/include/bsc/ebpf/EBPFMon.hpp. Campo `event_kind` aggiunto
75 * per distinguere la sorgente (tracepoint/kprobe); i vecchi loader che
76 * non lo conoscono leggeranno 0 nel padding, quindi retro-compat ok.
77 */
79 /* processo */
80 __u32 pid;
81 __u32 tid;
82 __u32 uid;
83 __u32 gid;
84 char comm[16]; /* task_comm_len */
85
86 /* rete */
87 __u8 family; /* AF_INET o AF_INET6 */
88 __u8 proto; /* IPPROTO_TCP=6 o IPPROTO_UDP=17 */
89 __u8 event_kind; /* 0=tp_set_state, 1=tcp_connect, 2=udp_sendmsg */
90 __u8 _pad[1];
91 __u16 src_port; /* host byte order */
92 __u16 dst_port; /* host byte order */
93
94 /* indirizzi: primi 4 byte validi per IPv4, tutti 16 per IPv6 */
95 __u8 src_addr[16];
96 __u8 dst_addr[16];
97
98 /* timestamp monotono (ns) */
99 __u64 ts_ns;
100};
101
102/* Chiave di dedup: pid + dst_ip + dst_port sufficiente.
103 * Non includiamo src_port perche' per UDP e' effimero e farebbe saltare
104 * il dedup. */
105struct dedup_key {
106 __u32 pid;
107 __u16 dst_port;
108 __u8 family;
109 __u8 proto;
110 __u8 dst_addr[16];
111};
112
113/* ── Maps ─────────────────────────────────────────────────────────── */
114struct {
115 __uint(type, BPF_MAP_TYPE_RINGBUF);
116 __uint(max_entries, RINGBUF_SIZE);
117} bsc_events SEC(".maps");
118
119struct {
120 __uint(type, BPF_MAP_TYPE_LRU_HASH);
121 __uint(max_entries, 8192);
122 __type(key, struct dedup_key);
123 __type(value, __u64); /* ts_ns ultimo emit */
124} bsc_dedup SEC(".maps");
125
126/* ── Helper: riempie i campi "processo" dell'evento ───────────────── */
127static __always_inline void fill_proc(struct bsc_conn_event* ev)
128{
129 const __u64 pid_tgid = bpf_get_current_pid_tgid();
130 ev->pid = (__u32)(pid_tgid >> 32);
131 ev->tid = (__u32)(pid_tgid & 0xffffffffull);
132 const __u64 uid_gid = bpf_get_current_uid_gid();
133 ev->uid = (__u32)(uid_gid & 0xffffffffull);
134 ev->gid = (__u32)(uid_gid >> 32);
135 bpf_get_current_comm(ev->comm, sizeof(ev->comm));
136}
137
138/* ── Helper: dedup check. Ritorna true se dobbiamo emettere. ──────── */
139static __always_inline bool should_emit(const struct dedup_key* k,
140 __u64 now_ns,
141 __u64 window_ns)
142{
143 __u64* last = bpf_map_lookup_elem(&bsc_dedup, k);
144 if (last) {
145 if (now_ns - *last < window_ns) return false; /* scarto: duplicato */
146 *last = now_ns; /* update in-place */
147 return true;
148 }
149 bpf_map_update_elem(&bsc_dedup, k, &now_ns, BPF_ANY);
150 return true;
151}
152
153/* =================================================================== *
154 * 1) tracepoint TCP state change (ESTABLISHED)
155 * =================================================================== */
156SEC("tracepoint/sock/inet_sock_set_state")
157int bsc_inet_sock_set_state(struct trace_event_raw_inet_sock_set_state *ctx)
158{
159 if (ctx->newstate != TCP_ESTABLISHED)
160 return 0;
161
162 const __u16 family = ctx->family;
163 if (family != AF_INET && family != AF_INET6)
164 return 0;
165
166 const __u8 proto = (__u8)ctx->protocol;
167 if (proto != IPPROTO_TCP && proto != IPPROTO_UDP)
168 return 0;
169
170 struct bsc_conn_event *ev = bpf_ringbuf_reserve(&bsc_events,
171 sizeof(*ev), 0);
172 if (!ev) return 0;
173
174 fill_proc(ev);
175 ev->family = (__u8)family;
176 ev->proto = proto;
177 ev->event_kind = 0;
178 ev->_pad[0] = 0;
179 ev->src_port = bpf_ntohs(ctx->sport);
180 ev->dst_port = bpf_ntohs(ctx->dport);
181
182 if (family == AF_INET) {
183 __builtin_memset(ev->src_addr, 0, sizeof(ev->src_addr));
184 __builtin_memset(ev->dst_addr, 0, sizeof(ev->dst_addr));
185 bpf_probe_read_kernel(ev->src_addr, 4, &ctx->saddr);
186 bpf_probe_read_kernel(ev->dst_addr, 4, &ctx->daddr);
187 } else {
188 BPF_CORE_READ_INTO(ev->src_addr, ctx, saddr_v6);
189 BPF_CORE_READ_INTO(ev->dst_addr, ctx, daddr_v6);
190 }
191
192 ev->ts_ns = bpf_ktime_get_ns();
193 bpf_ringbuf_submit(ev, 0);
194 return 0;
195}
196
197/* =================================================================== *
198 * 2) TCP connect (IPv4 + IPv6)
199 * =================================================================== *
200 *
201 * Firma: int tcp_v4_connect(struct sock* sk, struct sockaddr* uaddr, int len)
202 * int tcp_v6_connect(struct sock* sk, struct sockaddr* uaddr, int len)
203 *
204 * Emettiamo l'evento subito all'ingresso — abbiamo gia' tutti i dati
205 * (sk contiene src/dst). Il connect puo' ancora fallire ma per il
206 * firewall conta il tentativo.
207 */
208SEC("kprobe/tcp_v4_connect")
209int BPF_KPROBE(bsc_tcp_v4_connect, struct sock* sk)
210{
211 if (!sk) return 0;
212
213 const __u64 now = bpf_ktime_get_ns();
214
215 /* Leggi destinazione dalla struct sock (inet_sock → skc_daddr/dport).
216 * NOTA: in tcp_v4_connect la destinazione viene scritta prima
217 * della chiamata effettiva al connect — quindi e' gia' nel sk. */
218 __u32 daddr = BPF_CORE_READ(sk, __sk_common.skc_daddr);
219 __u32 saddr = BPF_CORE_READ(sk, __sk_common.skc_rcv_saddr);
220 __u16 dport = BPF_CORE_READ(sk, __sk_common.skc_dport); /* net byte order */
221 __u16 sport = BPF_CORE_READ(sk, __sk_common.skc_num); /* host byte order */
222 dport = bpf_ntohs(dport);
223
224 /* saddr puo' essere 0 (kernel non ha ancora scelto src): non e' errore */
225
226 struct dedup_key k = {};
227 const __u64 pid_tgid = bpf_get_current_pid_tgid();
228 k.pid = (__u32)(pid_tgid >> 32);
229 k.dst_port = dport;
230 k.family = AF_INET;
231 k.proto = IPPROTO_TCP;
232 __builtin_memcpy(k.dst_addr, &daddr, 4);
233 if (!should_emit(&k, now, TCP_DEDUP_WINDOW_NS)) return 0;
234
235 struct bsc_conn_event *ev = bpf_ringbuf_reserve(&bsc_events,
236 sizeof(*ev), 0);
237 if (!ev) return 0;
238
239 fill_proc(ev);
240 ev->family = AF_INET;
241 ev->proto = IPPROTO_TCP;
242 ev->event_kind = 1;
243 ev->_pad[0] = 0;
244 ev->src_port = sport;
245 ev->dst_port = dport;
246
247 __builtin_memset(ev->src_addr, 0, sizeof(ev->src_addr));
248 __builtin_memset(ev->dst_addr, 0, sizeof(ev->dst_addr));
249 __builtin_memcpy(ev->src_addr, &saddr, 4);
250 __builtin_memcpy(ev->dst_addr, &daddr, 4);
251
252 ev->ts_ns = now;
253 bpf_ringbuf_submit(ev, 0);
254 return 0;
255}
256
257SEC("kprobe/tcp_v6_connect")
258int BPF_KPROBE(bsc_tcp_v6_connect, struct sock* sk)
259{
260 if (!sk) return 0;
261
262 const __u64 now = bpf_ktime_get_ns();
263
264 __u16 dport = BPF_CORE_READ(sk, __sk_common.skc_dport);
265 __u16 sport = BPF_CORE_READ(sk, __sk_common.skc_num);
266 dport = bpf_ntohs(dport);
267
268 struct dedup_key k = {};
269 const __u64 pid_tgid = bpf_get_current_pid_tgid();
270 k.pid = (__u32)(pid_tgid >> 32);
271 k.dst_port = dport;
272 k.family = AF_INET6;
273 k.proto = IPPROTO_TCP;
274 BPF_CORE_READ_INTO(k.dst_addr, sk, __sk_common.skc_v6_daddr);
275 if (!should_emit(&k, now, TCP_DEDUP_WINDOW_NS)) return 0;
276
277 struct bsc_conn_event *ev = bpf_ringbuf_reserve(&bsc_events,
278 sizeof(*ev), 0);
279 if (!ev) return 0;
280
281 fill_proc(ev);
282 ev->family = AF_INET6;
283 ev->proto = IPPROTO_TCP;
284 ev->event_kind = 1;
285 ev->_pad[0] = 0;
286 ev->src_port = sport;
287 ev->dst_port = dport;
288
289 BPF_CORE_READ_INTO(ev->src_addr, sk, __sk_common.skc_v6_rcv_saddr);
290 BPF_CORE_READ_INTO(ev->dst_addr, sk, __sk_common.skc_v6_daddr);
291
292 ev->ts_ns = now;
293 bpf_ringbuf_submit(ev, 0);
294 return 0;
295}
296
297/* =================================================================== *
298 * 3) UDP sendmsg (IPv4 + IPv6)
299 * =================================================================== *
300 *
301 * Firma: int udp_sendmsg(struct sock* sk, struct msghdr* msg, size_t len)
302 * int udpv6_sendmsg(struct sock* sk, struct msghdr* msg, size_t len)
303 *
304 * Se il socket e' stato "connesso" con connect(), skc_daddr/skc_dport
305 * contengono la destinazione. Altrimenti la destinazione e' in
306 * msg->msg_name (sockaddr_in/6). Gestiamo entrambi i casi.
307 */
308
309/* Estrae dst_ip e dst_port v4 dalla msghdr se presente.
310 * Ritorna true se ha letto con successo. */
311static __always_inline bool extract_msg_v4(struct msghdr* msg,
312 __u32* out_daddr,
313 __u16* out_dport)
314{
315 if (!msg) return false;
316 void* name = BPF_CORE_READ(msg, msg_name);
317 int nlen = BPF_CORE_READ(msg, msg_namelen);
318 if (!name || nlen < (int)sizeof(struct sockaddr_in)) return false;
319
320 struct sockaddr_in sa = {};
321 if (bpf_probe_read_kernel(&sa, sizeof(sa), name) != 0) return false;
322 if (sa.sin_family != AF_INET) return false;
323
324 *out_daddr = sa.sin_addr.s_addr; /* network byte order */
325 *out_dport = bpf_ntohs(sa.sin_port);
326 return true;
327}
328
329static __always_inline bool extract_msg_v6(struct msghdr* msg,
330 __u8 out_daddr[16],
331 __u16* out_dport)
332{
333 if (!msg) return false;
334 void* name = BPF_CORE_READ(msg, msg_name);
335 int nlen = BPF_CORE_READ(msg, msg_namelen);
336 if (!name || nlen < (int)sizeof(struct sockaddr_in6)) return false;
337
338 struct sockaddr_in6 sa = {};
339 if (bpf_probe_read_kernel(&sa, sizeof(sa), name) != 0) return false;
340 if (sa.sin6_family != AF_INET6) return false;
341
342 __builtin_memcpy(out_daddr, &sa.sin6_addr, 16);
343 *out_dport = bpf_ntohs(sa.sin6_port);
344 return true;
345}
346
347SEC("kprobe/udp_sendmsg")
348int BPF_KPROBE(bsc_udp_sendmsg, struct sock* sk, struct msghdr* msg)
349{
350 if (!sk) return 0;
351
352 __u32 daddr = 0;
353 __u16 dport = 0;
354
355 /* 1) prova dalla msghdr (UDP unconnected) */
356 if (!extract_msg_v4(msg, &daddr, &dport)) {
357 /* 2) fallback: dalla struct sock (UDP connected) */
358 daddr = BPF_CORE_READ(sk, __sk_common.skc_daddr);
359 __u16 d = BPF_CORE_READ(sk, __sk_common.skc_dport);
360 dport = bpf_ntohs(d);
361 }
362 if (dport == 0) return 0; /* socket senza destinazione risolta */
363
364 const __u64 now = bpf_ktime_get_ns();
365 __u32 saddr = BPF_CORE_READ(sk, __sk_common.skc_rcv_saddr);
366 __u16 sport = BPF_CORE_READ(sk, __sk_common.skc_num);
367
368 struct dedup_key k = {};
369 const __u64 pid_tgid = bpf_get_current_pid_tgid();
370 k.pid = (__u32)(pid_tgid >> 32);
371 k.dst_port = dport;
372 k.family = AF_INET;
373 k.proto = IPPROTO_UDP;
374 __builtin_memcpy(k.dst_addr, &daddr, 4);
375 if (!should_emit(&k, now, UDP_DEDUP_WINDOW_NS)) return 0;
376
377 struct bsc_conn_event *ev = bpf_ringbuf_reserve(&bsc_events,
378 sizeof(*ev), 0);
379 if (!ev) return 0;
380
381 fill_proc(ev);
382 ev->family = AF_INET;
383 ev->proto = IPPROTO_UDP;
384 ev->event_kind = 2;
385 ev->_pad[0] = 0;
386 ev->src_port = sport;
387 ev->dst_port = dport;
388
389 __builtin_memset(ev->src_addr, 0, sizeof(ev->src_addr));
390 __builtin_memset(ev->dst_addr, 0, sizeof(ev->dst_addr));
391 __builtin_memcpy(ev->src_addr, &saddr, 4);
392 __builtin_memcpy(ev->dst_addr, &daddr, 4);
393
394 ev->ts_ns = now;
395 bpf_ringbuf_submit(ev, 0);
396 return 0;
397}
398
399SEC("kprobe/udpv6_sendmsg")
400int BPF_KPROBE(bsc_udpv6_sendmsg, struct sock* sk, struct msghdr* msg)
401{
402 if (!sk) return 0;
403
404 __u8 daddr[16] = {};
405 __u16 dport = 0;
406
407 if (!extract_msg_v6(msg, daddr, &dport)) {
408 /* fallback: socket connected */
409 BPF_CORE_READ_INTO(daddr, sk, __sk_common.skc_v6_daddr);
410 __u16 d = BPF_CORE_READ(sk, __sk_common.skc_dport);
411 dport = bpf_ntohs(d);
412 }
413 if (dport == 0) return 0;
414
415 const __u64 now = bpf_ktime_get_ns();
416 __u16 sport = BPF_CORE_READ(sk, __sk_common.skc_num);
417
418 struct dedup_key k = {};
419 const __u64 pid_tgid = bpf_get_current_pid_tgid();
420 k.pid = (__u32)(pid_tgid >> 32);
421 k.dst_port = dport;
422 k.family = AF_INET6;
423 k.proto = IPPROTO_UDP;
424 __builtin_memcpy(k.dst_addr, daddr, 16);
425 if (!should_emit(&k, now, UDP_DEDUP_WINDOW_NS)) return 0;
426
427 struct bsc_conn_event *ev = bpf_ringbuf_reserve(&bsc_events,
428 sizeof(*ev), 0);
429 if (!ev) return 0;
430
431 fill_proc(ev);
432 ev->family = AF_INET6;
433 ev->proto = IPPROTO_UDP;
434 ev->event_kind = 2;
435 ev->_pad[0] = 0;
436 ev->src_port = sport;
437 ev->dst_port = dport;
438
439 BPF_CORE_READ_INTO(ev->src_addr, sk, __sk_common.skc_v6_rcv_saddr);
440 __builtin_memcpy(ev->dst_addr, daddr, 16);
441
442 ev->ts_ns = now;
443 bpf_ringbuf_submit(ev, 0);
444 return 0;
445}
446
447char LICENSE[] SEC("license") = "GPL";
#define UDP_DEDUP_WINDOW_NS
Definition bsc_ebpf.c:68
static __always_inline void fill_proc(struct bsc_conn_event *ev)
Definition bsc_ebpf.c:127
#define TCP_DEDUP_WINDOW_NS
Definition bsc_ebpf.c:69
static __always_inline bool extract_msg_v4(struct msghdr *msg, __u32 *out_daddr, __u16 *out_dport)
Definition bsc_ebpf.c:311
static __always_inline bool should_emit(const struct dedup_key *k, __u64 now_ns, __u64 window_ns)
Definition bsc_ebpf.c:139
static __always_inline bool extract_msg_v6(struct msghdr *msg, __u8 out_daddr[16], __u16 *out_dport)
Definition bsc_ebpf.c:329
#define RINGBUF_SIZE
Definition bsc_ebpf.c:65
#define IPPROTO_TCP
Definition bsc_ebpf.c:60
#define AF_INET
Definition bsc_ebpf.c:56
#define AF_INET6
Definition bsc_ebpf.c:57
#define TCP_ESTABLISHED
Definition bsc_ebpf.c:58
#define IPPROTO_UDP
Definition bsc_ebpf.c:61
int BPF_KPROBE(bsc_tcp_v4_connect, struct sock *sk)
Definition bsc_ebpf.c:209
int bsc_inet_sock_set_state(struct trace_event_raw_inet_sock_set_state *ctx)
Definition bsc_ebpf.c:157
__type(key, __u32)
__uint(type, BPF_MAP_TYPE_RINGBUF)
struct rg_task_state SEC
__u8 _pad[1]
Definition bsc_ebpf.c:90
__u16 dst_port
Definition bsc_ebpf.c:92
__u8 src_addr[16]
Definition bsc_ebpf.c:95
__u8 dst_addr[16]
Definition bsc_ebpf.c:96
char comm[16]
Definition bsc_ebpf.c:84
__u8 event_kind
Definition bsc_ebpf.c:89
__u16 src_port
Definition bsc_ebpf.c:91
__u16 dst_port
Definition bsc_ebpf.c:107
__u8 dst_addr[16]
Definition bsc_ebpf.c:110
__u32 pid
Definition bsc_ebpf.c:106
__u8 proto
Definition bsc_ebpf.c:109
__u8 family
Definition bsc_ebpf.c:108