Files
ja4-platform/services/ja4ebpf/bpf/uprobe_ssl.c
toto f85a10b012 feat: pipeline L7 HTTP complet + infrastructure tests VM
Correctifs pipeline L7 (uprobe SSL_read) :
- uprobe_ssl.c : ssl_set_fd ne retourne plus tôt quand fd_conn_map est
  vide (accept4 non disponible en Docker). Sauvegarde ssl_ptr→{fd,0,0}
  pour permettre le fallback /proc côté Go.
- main.go : consumeSSLEvents reécrit avec routeur magic-bytes complet :
  * HTTP/2 preface → extraction SETTINGS + conversion correlation.HTTP2Settings
  * HTTP/1.x requête → method, path, query, headers, header_order_sig
  * HTTP/1.x réponse → status_code
  * Fallback /proc/<tgid>/fd/<fd> quand src_ip=0 (accept4 absent)
- writer/clickhouse.go : export header_order_signature ajouté

Nouveaux packages :
- internal/parser/http1.go : parseur HTTP/1.x (IsHTTP1Request,
  ParseHTTP1Request, IsHTTP1Response, ParseHTTP1Response)
- internal/parser/http1_test.go : 11 tests unitaires (28 total passent)
- internal/procutil/proc_lookup.go : résolution fd→IP via /proc avec cache
  TTL 5s (FDCache). Supporte /proc/PID/net/tcp et tcp6, IPv4-mappé IPv6.

Infrastructure tests VM (tests/vm/) :
- Vagrantfile : VM Rocky Linux 9 KVM, 4 CPU / 4 GB RAM
- provision.sh : installation toolchain eBPF + Go + Docker + nginx
- run-tests-vm.sh : suite de test complète dans la VM (L3/L4+TLS+L7)
- README.md : guide d'installation et d'utilisation
- Makefile : cibles vm-up, vm-down, vm-ssh, test-vm-nginx, test-vm-all,
  vm-rebuild-ja4ebpf

Corrections stack Docker :
- Dockerfiles nginx/apache/nginx-varnish/hitch-varnish : suppression des
  références à shared/go/ja4common/ (répertoire supprimé)
- clickhouse-init.sh : restauré depuis git, seed anubis_ua_rules obsolète
  supprimé (table REGEXP_TREE supprimée du schéma)
- traffic-gen : ajout HTTP/1.0 (http.client) et HTTP/2 (httpx)
- verify_db.py : script de vérification 35 checks (L3/L4/TLS/L7/corrélation)
- run-stack-tests.sh : phase 6 verify_db ajoutée

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-12 02:37:00 +02:00

259 lines
9.2 KiB
C

/* uprobe_ssl.c — Uprobes SSL_read/SSL_set_fd et tracepoints accept4
*
* Intercepte les appels OpenSSL pour capturer le trafic déchiffré,
* et corrige l'association socket ↔ SSL* via les tracepoints syscalls/accept4.
* Les tracepoints sont plus stables que les kprobes car ils ne dépendent pas
* du nom manglé __x64_sys_accept4 (variable selon la version du kernel).
* ============================================================================ */
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <bpf/bpf_core_read.h>
#include "bpf_types.h"
/* Taille maximale de données SSL à copier par événement */
#define MAX_SSL_DATA 4096
/* ---------------------------------------------------------------------------
* Map temporaire : pid_tgid → upeer_sockaddr (sauvegardé à l'entrée d'accept4)
* ---------------------------------------------------------------------------*/
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 10240);
__type(key, __u64);
__type(value, __u64); /* pointeur userspace vers sockaddr_in */
} accept_args_map SEC(".maps");
/* ---------------------------------------------------------------------------
* Structs pour les tracepoints syscalls/sys_{enter,exit}_accept4
*
* Format vérifié avec : /sys/kernel/tracing/events/syscalls/sys_enter_accept4/format
* Valable pour les kernels 4.x → 6.x (stable, CO-RE non requis).
* ---------------------------------------------------------------------------*/
struct sys_enter_accept4_ctx {
__u16 common_type;
__u8 common_flags;
__u8 common_preempt_count;
__s32 common_pid;
__s32 __syscall_nr;
__u32 _pad;
__s64 listen_fd;
struct sockaddr *upeer_sockaddr; /* adresse userspace du client */
int *upeer_addrlen;
__s64 flags;
};
struct sys_exit_accept4_ctx {
__u16 common_type;
__u8 common_flags;
__u8 common_preempt_count;
__s32 common_pid;
__s32 __syscall_nr;
__u32 _pad;
__s64 ret; /* fd retourné par accept4, ou < 0 si erreur */
};
/* ===========================================================================
* uprobe_ssl_set_fd — Intercept SSL_set_fd(SSL *s, int fd)
*
* Associe un ssl_ptr à ses informations de connexion (fd, src_ip, src_port)
* en consultant fd_conn_map. Si fd_conn_map est vide (accept4 non disponible),
* enregistre quand même l'association ssl_ptr → fd avec IP=0 pour que le
* Go userspace puisse faire le lookup IP via /proc/<pid>/net/tcp.
* ===========================================================================*/
SEC("uprobe/SSL_set_fd")
int uprobe_ssl_set_fd(struct pt_regs *ctx)
{
__u64 ssl_ptr = ((__u64)PT_REGS_PARM1(ctx));
__u32 fd = ((__u32)PT_REGS_PARM2(ctx));
struct ssl_conn_info new_conn = {};
new_conn.fd = fd;
/* Tenter de récupérer les infos de connexion via fd_conn_map (accept4) */
struct ssl_conn_info *conn = bpf_map_lookup_elem(&fd_conn_map, &fd);
if (conn) {
new_conn.src_ip = conn->src_ip;
new_conn.src_port = conn->src_port;
}
/* Sans accept4, src_ip=0 / src_port=0 — le userspace Go fera le lookup /proc */
bpf_map_update_elem(&ssl_conn_map, &ssl_ptr, &new_conn, BPF_ANY);
return 0;
}
/* ===========================================================================
* uprobe_ssl_read_entry — Entrée de SSL_read(SSL *ssl, void *buf, int num)
*
* Sauvegarde les arguments pour l'uretprobe correspondant.
* ===========================================================================*/
SEC("uprobe/SSL_read")
int uprobe_ssl_read_entry(struct pt_regs *ctx)
{
__u64 pid_tgid = bpf_get_current_pid_tgid();
struct ssl_read_args args = {};
args.ssl_ptr = (__u64)PT_REGS_PARM1(ctx);
args.buf_ptr = (__u64)PT_REGS_PARM2(ctx);
args.num = (__u32)PT_REGS_PARM3(ctx);
bpf_map_update_elem(&ssl_args_map, &pid_tgid, &args, BPF_ANY);
return 0;
}
/* ===========================================================================
* uretprobe_ssl_read_exit — Retour de SSL_read
*
* Lit le buffer déchiffré et l'émet dans rb_ssl_data.
* ===========================================================================*/
SEC("uretprobe/SSL_read")
int uretprobe_ssl_read_exit(struct pt_regs *ctx)
{
__u64 pid_tgid = bpf_get_current_pid_tgid();
/* Récupérer les arguments sauvegardés à l'entrée */
struct ssl_read_args *args = bpf_map_lookup_elem(&ssl_args_map, &pid_tgid);
if (!args)
return 0;
/* Vérifier que la lecture a réussi (valeur de retour > 0) */
long retval = PT_REGS_RC(ctx);
if (retval <= 0) {
bpf_map_delete_elem(&ssl_args_map, &pid_tgid);
return 0;
}
/* Allouer un slot dans le ring buffer */
struct ssl_data_event *evt = bpf_ringbuf_reserve(&rb_ssl_data, sizeof(*evt), 0);
if (!evt) {
bpf_map_delete_elem(&ssl_args_map, &pid_tgid);
return 0;
}
evt->pid_tgid = pid_tgid;
evt->direction = 0; /* lecture = client vers serveur */
evt->timestamp_ns = bpf_ktime_get_ns();
/* Limiter la copie à MAX_SSL_DATA octets */
__u32 data_len = (retval > MAX_SSL_DATA) ? MAX_SSL_DATA : (__u32)retval;
evt->data_len = data_len;
/* Copier depuis l'espace utilisateur */
bpf_probe_read_user(evt->data, data_len & (MAX_SSL_DATA - 1), (void *)args->buf_ptr);
/* Retrouver les infos de connexion via ssl_ptr */
struct ssl_conn_info *conn = bpf_map_lookup_elem(&ssl_conn_map, &args->ssl_ptr);
if (conn) {
evt->fd = conn->fd;
evt->src_ip = conn->src_ip;
evt->src_port = conn->src_port;
} else {
evt->fd = 0;
evt->src_ip = 0;
evt->src_port = 0;
}
bpf_ringbuf_submit(evt, 0);
bpf_map_delete_elem(&ssl_args_map, &pid_tgid);
return 0;
}
/* ===========================================================================
* kprobe_accept4_entry — Entrée de accept4 via tracepoint syscalls
*
* Utilise SEC("tracepoint/syscalls/sys_enter_accept4") au lieu d'un kprobe
* pour éviter la dépendance au nom manglé __x64_sys_accept4 (kernel 5.1+).
* Le contexte tracepoint expose directement upeer_sockaddr sans indirection.
* ===========================================================================*/
SEC("tracepoint/syscalls/sys_enter_accept4")
int kprobe_accept4_entry(struct sys_enter_accept4_ctx *ctx)
{
__u64 pid_tgid = bpf_get_current_pid_tgid();
__u64 sockaddr_ptr = (__u64)ctx->upeer_sockaddr;
bpf_map_update_elem(&accept_args_map, &pid_tgid, &sockaddr_ptr, BPF_ANY);
return 0;
}
/* ===========================================================================
* kretprobe_accept4_exit — Retour de accept4 via tracepoint syscalls
*
* Lit la sockaddr_in pour extraire src_ip:src_port du client,
* peuple accept_map et fd_conn_map, et émet dans rb_accept.
* ===========================================================================*/
SEC("tracepoint/syscalls/sys_exit_accept4")
int kretprobe_accept4_exit(struct sys_exit_accept4_ctx *ctx)
{
__u64 pid_tgid = bpf_get_current_pid_tgid();
/* Vérifier que accept4 a réussi (fd ≥ 0) */
long new_fd = ctx->ret;
if (new_fd < 0) {
bpf_map_delete_elem(&accept_args_map, &pid_tgid);
return 0;
}
/* Récupérer le pointeur vers sockaddr_in */
__u64 *sockaddr_ptr_p = bpf_map_lookup_elem(&accept_args_map, &pid_tgid);
if (!sockaddr_ptr_p) {
return 0;
}
__u64 sockaddr_ptr = *sockaddr_ptr_p;
bpf_map_delete_elem(&accept_args_map, &pid_tgid);
if (!sockaddr_ptr)
return 0;
/* Lire la structure sockaddr_in depuis l'espace utilisateur */
/* struct sockaddr_in: sin_family(2) + sin_port(2) + sin_addr(4) */
__u8 sa_buf[8] = {};
bpf_probe_read_user(sa_buf, sizeof(sa_buf), (void *)sockaddr_ptr);
/* Extraire port (octets 2-3) et adresse IP (octets 4-7) */
__u16 sin_port = (__u16)(sa_buf[2] << 8) | sa_buf[3]; /* network byte order */
__u32 sin_addr = *(__u32 *)(sa_buf + 4); /* network byte order */
__u32 src_ip = __builtin_bswap32(sin_addr); /* host byte order */
__u16 src_port = __builtin_bswap16(sin_port); /* host byte order */
__u32 fd = (__u32)new_fd;
/* Peupler accept_map[{pid_tgid, fd}] */
struct accept_key akey = { .pid_tgid = pid_tgid, .fd = fd };
struct accept_event aevt = {
.pid_tgid = pid_tgid,
.fd = fd,
.src_ip = src_ip,
.src_port = src_port,
.timestamp_ns = bpf_ktime_get_ns(),
};
bpf_map_update_elem(&accept_map, &akey, &aevt, BPF_ANY);
/* Peupler fd_conn_map[fd] pour accès rapide par SSL_set_fd */
struct ssl_conn_info conn_info = {
.fd = fd,
.src_ip = src_ip,
.src_port = src_port,
};
bpf_map_update_elem(&fd_conn_map, &fd, &conn_info, BPF_ANY);
/* Émettre dans rb_accept */
struct accept_event *out = bpf_ringbuf_reserve(&rb_accept, sizeof(*out), 0);
if (!out)
return 0;
out->pid_tgid = pid_tgid;
out->fd = fd;
out->src_ip = src_ip;
out->src_port = src_port;
out->timestamp_ns = aevt.timestamp_ns;
bpf_ringbuf_submit(out, 0);
return 0;
}
char LICENSE[] SEC("license") = "GPL";