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:
@ -119,6 +119,26 @@ struct nginx_http_event {
|
||||
__u32 data_len; /* longueur données brutes */
|
||||
} __attribute__((packed));
|
||||
|
||||
/* ---------------------------------------------------------------------------
|
||||
* Événement Apache HTTP : requête HTTP complète depuis Apache httpd
|
||||
* ---------------------------------------------------------------------------*/
|
||||
struct apache_http_event {
|
||||
__u64 pid_tgid; /* PID+TGID du processus Apache */
|
||||
__u32 fd; /* descripteur de fichier socket (corrélation TC) */
|
||||
__u32 src_ip; /* IP source du client (host byte order) */
|
||||
__u16 src_port; /* port source du client */
|
||||
__u64 timestamp_ns; /* horodatage kernel */
|
||||
__u8 http_method[16]; /* méthode HTTP (GET, POST, etc.) */
|
||||
__u8 uri[256]; /* URI demandée (sans query string) */
|
||||
__u8 query[128]; /* query string */
|
||||
__u8 data[3640]; /* données HTTP brutes (headers + body) */
|
||||
__u32 method_len; /* longueur méthode */
|
||||
__u32 uri_len; /* longueur URI */
|
||||
__u32 query_len; /* longueur query string */
|
||||
__u32 body_len; /* longueur body */
|
||||
__u32 data_len; /* longueur données brutes */
|
||||
} __attribute__((packed));
|
||||
|
||||
/* ---------------------------------------------------------------------------
|
||||
* Arguments sauvegardés à l'entrée de read() (pour l'uretprobe nginx)
|
||||
* ---------------------------------------------------------------------------*/
|
||||
@ -128,6 +148,15 @@ struct nginx_read_args {
|
||||
__u64 count; /* taille maximale demandée */
|
||||
} __attribute__((packed));
|
||||
|
||||
/* ---------------------------------------------------------------------------
|
||||
* Arguments sauvegardés à l'entrée de read() pour Apache httpd
|
||||
* ---------------------------------------------------------------------------*/
|
||||
struct read_args {
|
||||
__s32 fd; /* descripteur de fichier */
|
||||
__u64 buf_ptr; /* pointeur vers buffer de réception */
|
||||
__u64 count; /* taille maximale demandée */
|
||||
} __attribute__((packed));
|
||||
|
||||
/* ---------------------------------------------------------------------------
|
||||
* Arguments sauvegardés à l'entrée de SSL_read (pour l'uretprobe)
|
||||
* ---------------------------------------------------------------------------*/
|
||||
@ -281,3 +310,38 @@ struct {
|
||||
__type(value, struct nginx_http_event);
|
||||
} __nginx_buf SEC(".maps");
|
||||
|
||||
/* ===========================================================================
|
||||
* Apache httpd HTTP capture via read() syscall
|
||||
* =========================================================================== */
|
||||
|
||||
/* Hash map : PID Apache → flag pour filtrage read (tracepoints) */
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_HASH);
|
||||
__uint(max_entries, 16);
|
||||
__type(key, __u32);
|
||||
__type(value, __u8);
|
||||
} apache_pid_map SEC(".maps");
|
||||
|
||||
/* Hash map : pid_tgid → read_args (arguments read entry pour Apache) */
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_HASH);
|
||||
__uint(max_entries, 10240);
|
||||
__type(key, __u64);
|
||||
__type(value, struct read_args);
|
||||
} apache_read_args_map SEC(".maps");
|
||||
|
||||
/* PERCPU_ARRAY temporaire pour apache_http_event (taille ~4KB) */
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
|
||||
__uint(max_entries, 1);
|
||||
__type(key, __u32);
|
||||
__type(value, struct apache_http_event);
|
||||
} __apache_buf SEC(".maps");
|
||||
|
||||
/* PerfEventArray pour événements Apache HTTP */
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY);
|
||||
__uint(key_size, sizeof(__u32));
|
||||
__uint(value_size, sizeof(struct apache_http_event));
|
||||
} pb_apache_http SEC(".maps");
|
||||
|
||||
|
||||
115
services/ja4ebpf/bpf/uprobe_apache.c
Normal file
115
services/ja4ebpf/bpf/uprobe_apache.c
Normal 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";
|
||||
Reference in New Issue
Block a user