feat(ebpf): add Apache httpd HTTP capture via read() syscall

Add support for capturing HTTP traffic from Apache httpd using
tracepoint/kretprobe on read() syscall.

Changes:
- bpf/uprobe_apache.c: New BPF program for Apache httpd capture
  - Uses tp/syscalls/sys_enter_read to save arguments
  - Uses kretprobe/__x64_sys_read to capture data (avoids tracepoint exit issues)
- bpf/bpf_types.h: Add Apache-specific structures and maps
  - struct apache_http_event (same structure as nginx_http_event)
  - struct read_args (shared between enter/exit)
  - apache_pid_map for filtering by PID
  - apache_read_args_map for argument storage
  - pb_apache_http perf buffer
- internal/loader/loader.go: Add Apache support
  - Add Ja4ApacheObjects, apachePidMap, ApacheHTTPReader
  - Add go:generate directive for uprobe_apache.c
  - Add AttachUprobesApache(), AddApachePid(), RemoveApachePid()
  - Add findApachePIDs() to discover Apache httpd processes
- cmd/ja4ebpf/main.go: Add Apache runtime support
  - Add ApacheEnabled config option
  - Add attachApacheUprobesWithRetry() with automatic retry
  - Add consumeApacheHTTPEvents() to process Apache HTTP events
  - Add apache counter to eventCounters
  - Update debugStatsDumper to show apache events

Configuration:
- Enable with: uprobes.apache_enabled=true or JA4EBPF_APACHE_ENABLED=1
- Automatically discovers httpd/apache2 processes via /proc/[pid]/cmdline

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jacquin Antoine
2026-04-20 13:38:58 +02:00
parent 382683710a
commit 7dfe640003
4 changed files with 491 additions and 5 deletions

View File

@ -0,0 +1,115 @@
/* uprobe_apache.c — Tracepoints syscall pour capturer le trafic HTTP depuis Apache httpd
*
* Cette version utilise les tracepoints kernel syscalls/sys_enter_read et
* kretprobe sur __x64_sys_read pour capturer les appels système read() du serveur Apache.
* Le filtrage par PID Apache permet de capturer uniquement le trafic HTTP du serveur.
*
* ============================================================================
*/
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include "bpf_types.h"
/* Taille maximale d'une capture read() */
#define MAX_READ_SIZE 4096
/* ============================================================================
* tracepoint_sys_enter_read — Entrée du syscall read
*
* Sauvegarde les arguments si le PID correspond à Apache.
* Signature: ssize_t read(int fd, void *buf, size_t count);
* ============================================================================
*/
SEC("tp/syscalls/sys_enter_read")
int tp_sys_enter_read(struct trace_event_raw_sys_enter *ctx)
{
__u64 pid_tgid = bpf_get_current_pid_tgid();
__u32 pid = pid_tgid >> 32;
/* Vérifier si ce PID est dans la map apache_pid_map */
__u32 pid_key = pid;
__u8 *enabled = bpf_map_lookup_elem(&apache_pid_map, &pid_key);
if (!enabled || *enabled == 0) {
return 0; /* Pas un PID Apache, ignore */
}
/* Sauvegarder les arguments pour l'exit tracepoint */
struct read_args args = {};
args.fd = (__s32)ctx->args[0]; /* fd */
args.buf_ptr = (__u64)ctx->args[1]; /* buf */
args.count = (__u64)ctx->args[2]; /* count */
bpf_map_update_elem(&apache_read_args_map, &pid_tgid, &args, BPF_ANY);
return 0;
}
/* ============================================================================
* kretprobe_sys_exit_read — Sortie du syscall read
*
* Capture les données lues et les envoie vers pb_apache_http.
* Utilise kretprobe pour contourner les limitations de tracepoint exit.
* ============================================================================
*/
SEC("kretprobe/__x64_sys_read")
int kretprobe_sys_exit_read(struct pt_regs *ctx)
{
__u64 pid_tgid = bpf_get_current_pid_tgid();
/* Récupérer les arguments sauvegardés */
struct read_args *args = bpf_map_lookup_elem(&apache_read_args_map, &pid_tgid);
if (!args) {
return 0; /* Pas d'arguments correspondants */
}
/* Obtenir la valeur de retour (nombre d'octets lus) */
long retval = PT_REGS_RC(ctx);
if (retval <= 0 || retval > MAX_READ_SIZE) {
/* Erreur, EOF, ou trop de données - nettoyer et sortir */
bpf_map_delete_elem(&apache_read_args_map, &pid_tgid);
return 0;
}
/* Taille à copier (minimum entre retval et la taille disponible) */
__u64 copy_size = retval;
if (copy_size > args->count) {
copy_size = args->count;
}
/* Préparer l'événement Apache HTTP */
struct apache_http_event *e = bpf_map_lookup_elem(&__apache_buf, &pid_tgid);
if (!e) {
bpf_map_delete_elem(&apache_read_args_map, &pid_tgid);
return 0;
}
/* Initialiser l'événement */
__builtin_memset(e, 0, sizeof(*e));
e->pid_tgid = pid_tgid;
e->fd = args->fd;
e->timestamp_ns = bpf_ktime_get_ns();
/* Récupérer les infos de connexion depuis fd_conn_map */
struct ssl_conn_info *conn_info = bpf_map_lookup_elem(&fd_conn_map, &args->fd);
if (conn_info) {
e->src_ip = conn_info->src_ip;
e->src_port = conn_info->src_port;
}
/* Copier les données HTTP depuis l'espace utilisateur */
__u64 bytes_read = bpf_probe_read_user_str(e->data, sizeof(e->data), (void *)args->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));
}
/* Nettoyer */
bpf_map_delete_elem(&apache_read_args_map, &pid_tgid);
return 0;
}
char LICENSE[] SEC("license") = "GPL";