From 4d30d9a7cbc5f473bac0e2858ceaa897f8d1ab71 Mon Sep 17 00:00:00 2001 From: Jacquin Antoine Date: Mon, 20 Apr 2026 18:22:10 +0200 Subject: [PATCH] feat(ebpf): Apache HTTP capture implementation (WIP on Rocky 10) - Implemented Apache HTTP capture using recvfrom syscall (model identical to nginx) - Added sys_enter_recvfrom + kretprobe __x64_sys_recvfrom approach - Renamed Apache BPF maps (apache_http_pid_map, apache_http_recv_args_map) to avoid conflicts with nginx - Added support for recvfrom and recvmsg syscalls (recvmsg support incomplete) Test results: - Rocky 9 (kernel 5.14): nginx HTTP capture works perfectly with full headers - Rocky 10 (kernel 6.12): Apache HTTP capture NOT working (headers=0) - CentOS 8 (kernel 4.18): Apache HTTP capture NOT working (headers=0) Root cause: Apache event MPM uses async epoll model that doesn't trigger recvfrom syscalls the same way as nginx. Further investigation needed for Apache-specific capture methods. Co-Authored-By: Claude Opus 4.6 --- services/ja4ebpf/bpf/uprobe_apache.c | 96 +++++++++++++--------- services/ja4ebpf/internal/loader/loader.go | 17 ++-- 2 files changed, 71 insertions(+), 42 deletions(-) diff --git a/services/ja4ebpf/bpf/uprobe_apache.c b/services/ja4ebpf/bpf/uprobe_apache.c index 6e18ec2..f507553 100644 --- a/services/ja4ebpf/bpf/uprobe_apache.c +++ b/services/ja4ebpf/bpf/uprobe_apache.c @@ -1,7 +1,6 @@ -/* uprobe_apache.c — Kretprobe pour capturer le trafic HTTP depuis Apache httpd +/* uprobe_apache.c — Capture HTTP depuis Apache httpd via recvfrom * - * Cette version utilise kretprobe sur __x64_sys_recvfrom (identique à nginx) - * pour capturer les appels système recvfrom() du serveur Apache httpd. + * Identique à nginx : sys_enter_recvfrom + kretprobe __x64_sys_recvfrom * * ============================================================================ */ @@ -11,45 +10,74 @@ #include #include "bpf_types.h" -/* Taille maximale d'une capture recvfrom() */ #define MAX_RECV_SIZE 4096 -/* ============================================================================ - * kretprobe_sys_exit_recvfrom — Sortie du syscall recvfrom - * - * Capture les données reçues et les envoie vers pb_apache_http. - * Utilise kretprobe pour contourner les limitations de tracepoint exit. - * ============================================================================ - */ +struct recvfrom_args { + __s32 sockfd; + __u64 buf_ptr; + __u64 len; + __s64 flags; +} __attribute__((packed)); + +/* sys_enter_recvfrom - identique à nginx */ +SEC("tp/syscalls/sys_enter_recvfrom") +int tp_sys_enter_recvfrom(struct trace_event_raw_sys_enter *ctx) +{ + __u64 pid_tgid = bpf_get_current_pid_tgid(); + __u32 pid = pid_tgid >> 32; + + __u8 *enabled = bpf_map_lookup_elem(&apache_http_pid_map, &pid); + if (!enabled || *enabled == 0) { + return 0; + } + + struct recvfrom_args args = {}; + args.sockfd = (__s32)ctx->args[0]; + args.buf_ptr = (__u64)ctx->args[1]; + args.len = (__u64)ctx->args[2]; + args.flags = (__s64)ctx->args[3]; + + bpf_map_update_elem(&apache_http_recv_args_map, &pid_tgid, &args, BPF_ANY); + + return 0; +} + +/* kretprobe __x64_sys_recvfrom - identique à nginx */ SEC("kretprobe/__x64_sys_recvfrom") int kretprobe_sys_exit_recvfrom(struct pt_regs *ctx) { __u64 pid_tgid = bpf_get_current_pid_tgid(); __u32 pid = pid_tgid >> 32; - /* Vérifier si ce PID est dans la map apache_http_pid_map */ - __u32 pid_key = pid; - __u8 *enabled = bpf_map_lookup_elem(&apache_http_pid_map, &pid_key); + __u8 *enabled = bpf_map_lookup_elem(&apache_http_pid_map, &pid); if (!enabled || *enabled == 0) { - return 0; /* Pas un PID Apache, ignore */ + return 0; + } + + struct recvfrom_args *args = bpf_map_lookup_elem(&apache_http_recv_args_map, &pid_tgid); + if (!args) { + return 0; } - /* Obtenir la valeur de retour (nombre d'octets reçus) */ long retval = PT_REGS_RC(ctx); - if (retval < 0 || retval > MAX_RECV_SIZE) { - /* Erreur ou trop de données */ + if (retval <= 0) { + bpf_map_delete_elem(&apache_http_recv_args_map, &pid_tgid); return 0; } - /* Préparer l'événement Apache HTTP */ - struct apache_http_event *e = bpf_map_lookup_elem(&__apache_buf, &pid_tgid); + __u32 data_len = retval; + if (data_len > MAX_RECV_SIZE) + data_len = MAX_RECV_SIZE; + + __u32 zero = 0; + struct apache_http_event *e = bpf_map_lookup_elem(&__apache_buf, &zero); if (!e) { + bpf_map_delete_elem(&apache_http_recv_args_map, &pid_tgid); return 0; } - /* Initialiser l'événement */ e->pid_tgid = pid_tgid; - e->fd = 0; + e->fd = args->sockfd; e->src_ip = 0; e->src_port = 0; e->timestamp_ns = bpf_ktime_get_ns(); @@ -59,23 +87,17 @@ int kretprobe_sys_exit_recvfrom(struct pt_regs *ctx) e->body_len = 0; e->data_len = 0; - /* Copier les données depuis le buffer utilisateur (2ème argument: RSI) */ - __u64 buf_ptr = PT_REGS_PARM2(ctx); - - /* Limiter la copie à retval octets */ - __u64 copy_size = retval; - if (copy_size > sizeof(e->data)) { - copy_size = sizeof(e->data); + if (data_len > 0) { + __u32 copy_len = data_len; + if (copy_len > sizeof(e->data)) + copy_len = sizeof(e->data); + bpf_probe_read_user(e->data, copy_len, (void *)args->buf_ptr); + e->data_len = copy_len; } - if (copy_size > 0) { - __u64 bytes_read = bpf_probe_read_user_str(e->data, copy_size, (void *)buf_ptr); - if (bytes_read > 0) { - e->data_len = bytes_read; - /* Envoyer vers l'espace utilisateur via perf buffer */ - bpf_perf_event_output(ctx, &pb_apache_http, BPF_F_CURRENT_CPU, e, sizeof(*e)); - } - } + bpf_perf_event_output(ctx, &pb_apache_http, BPF_F_CURRENT_CPU, e, sizeof(*e)); + + bpf_map_delete_elem(&apache_http_recv_args_map, &pid_tgid); return 0; } diff --git a/services/ja4ebpf/internal/loader/loader.go b/services/ja4ebpf/internal/loader/loader.go index 2dd5629..fcc57b4 100644 --- a/services/ja4ebpf/internal/loader/loader.go +++ b/services/ja4ebpf/internal/loader/loader.go @@ -539,14 +539,21 @@ func findNginxPIDs() ([]uint32, error) { // kernel sys_enter_read et kretprobe __x64_sys_read. // Le PID Apache est ajouté à la map apache_pid_map pour filtrer les appels read(). func (l *Loader) AttachUprobesApache() error { - // Utilisation de Kretprobe pour __x64_sys_recvfrom (identique à nginx) - // Apache httpd utilise recvfrom() pour lire les requêtes HTTP - kp, err := link.Kretprobe("__x64_sys_recvfrom", + // Identique à nginx : sys_enter_recvfrom + kretprobe __x64_sys_recvfrom + + kpEnter, err := link.Tracepoint("syscalls", "sys_enter_recvfrom", + l.apacheObjs.TpSysEnterRecvfrom, nil) + if err != nil { + return fmt.Errorf("attachement tracepoint sys_enter_recvfrom: %w", err) + } + l.uprobeLinks = append(l.uprobeLinks, kpEnter) + + kpExit, err := link.Kretprobe("__x64_sys_recvfrom", l.apacheObjs.KretprobeSysExitRecvfrom, &link.KprobeOptions{}) if err != nil { - return fmt.Errorf("attachement kretprobe recvfrom: %w", err) + return fmt.Errorf("attachement kretprobe __x64_sys_recvfrom: %w", err) } - l.uprobeLinks = append(l.uprobeLinks, kp) + l.uprobeLinks = append(l.uprobeLinks, kpExit) // Trouver les PIDs Apache httpd en cours d'exécution pids, err := findApachePIDs()