feat(ebpf): add nginx uprobes skeleton for HTTP L7 capture
Add initial implementation of nginx uprobes to capture complete HTTP headers at application layer. This addresses the limitation of TC-based capture which truncates headers spanning multiple packets. Changes: - Add uprobe_nginx.c with read() syscall interception - Add nginx_read_args map for uretprobe correlation - Add AttachUprobesNginx() method with retry support - Config via uprobes.enabled in YAML or JA4EBPF_UPROBES_ENABLED env var Current status: - ✅ HTTPS (TLS) capture works perfectly - complete headers via SSL_read - ❌ HTTP plain nginx uprobes don't fire - nginx uses recv() not read() - ⚠️ HTTP plain TC capture truncates headers (fundamental limitation) Note: The nginx uprobes approach has limitations: 1. nginx uses recv()/recvmsg() syscalls, not read() 2. PLT attachment to glibc recv() doesn't trigger properly 3. Consider kprobes on sys_recvfrom or packet reassembly for future Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@ -99,6 +99,35 @@ struct http_plain_event {
|
||||
__u64 timestamp_ns; /* horodatage kernel */
|
||||
} __attribute__((packed));
|
||||
|
||||
/* ---------------------------------------------------------------------------
|
||||
* Événement Nginx HTTP : requête HTTP complète depuis nginx (via uprobes)
|
||||
* ---------------------------------------------------------------------------*/
|
||||
struct nginx_http_event {
|
||||
__u64 pid_tgid; /* PID+TGID du processus nginx */
|
||||
__u32 fd; /* descripteur de fichier socket (corrélation TC) */
|
||||
__u32 src_ip; /* IP source du client (host byte order, corrélation TC) */
|
||||
__u16 src_port; /* port source du client (corrélation TC) */
|
||||
__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 (0 pour l'instant) */
|
||||
__u32 data_len; /* longueur données brutes */
|
||||
} __attribute__((packed));
|
||||
|
||||
/* ---------------------------------------------------------------------------
|
||||
* Arguments sauvegardés à l'entrée de read() (pour l'uretprobe nginx)
|
||||
* ---------------------------------------------------------------------------*/
|
||||
struct nginx_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)
|
||||
* ---------------------------------------------------------------------------*/
|
||||
@ -164,6 +193,13 @@ struct {
|
||||
__uint(value_size, sizeof(__u32));
|
||||
} pb_http_plain SEC(".maps");
|
||||
|
||||
/* Perf event array : requêtes HTTP complètes depuis nginx (kernel 4.4+) */
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY);
|
||||
__uint(key_size, sizeof(__u32));
|
||||
__uint(value_size, sizeof(__u32));
|
||||
} pb_ginx_http SEC(".maps");
|
||||
|
||||
/* ── PERCPU_ARRAY temporaires pour les structs > 512o (stack eBPF) ──── */
|
||||
/* TLS hello event : 2064 octets, ne tient pas sur la stack */
|
||||
struct {
|
||||
@ -221,3 +257,19 @@ struct {
|
||||
__type(value, struct ssl_conn_info);
|
||||
} fd_conn_map SEC(".maps");
|
||||
|
||||
/* Hash map : pid_tgid → nginx_read_args (arguments read entry pour nginx) */
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_HASH);
|
||||
__uint(max_entries, 10240);
|
||||
__type(key, __u64);
|
||||
__type(value, struct nginx_read_args);
|
||||
} nginx_read_args_map SEC(".maps");
|
||||
|
||||
/* PERCPU_ARRAY temporaire pour nginx_http_event (taille ~4KB) */
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
|
||||
__uint(max_entries, 1);
|
||||
__type(key, __u32);
|
||||
__type(value, struct nginx_http_event);
|
||||
} __nginx_buf SEC(".maps");
|
||||
|
||||
|
||||
110
services/ja4ebpf/bpf/uprobe_nginx.c
Normal file
110
services/ja4ebpf/bpf/uprobe_nginx.c
Normal file
@ -0,0 +1,110 @@
|
||||
/* uprobe_nginx.c — Uprobes simplifiés pour capturer le trafic HTTP depuis nginx
|
||||
*
|
||||
* Version simplifiée qui utilise :
|
||||
* - read() syscall pour capturer les données lues par nginx depuis le socket
|
||||
* - Corrélation via fd entre TC (métadonnées L3/L4) et nginx (données L7)
|
||||
*
|
||||
* ============================================================================
|
||||
*/
|
||||
|
||||
#include "vmlinux.h"
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_tracing.h>
|
||||
#include "bpf_types.h"
|
||||
|
||||
/* Taille maximale d'une capture read() nginx */
|
||||
#define MAX_NGINX_READ_SIZE 4096
|
||||
|
||||
/* ============================================================================
|
||||
* uprobe_read_entry — Entrée de read() dans nginx
|
||||
*
|
||||
* Sauvegarde les arguments (fd, buf, count) pour l'uretprobe correspondant.
|
||||
* ============================================================================
|
||||
*/
|
||||
SEC("uprobe/read_entry")
|
||||
int uprobe_read_entry(struct pt_regs *ctx)
|
||||
{
|
||||
__u64 pid_tgid = bpf_get_current_pid_tgid();
|
||||
|
||||
/* Sauvegarder les arguments pour l'uretprobe */
|
||||
struct nginx_read_args args = {};
|
||||
args.fd = (__s32)PT_REGS_PARM1(ctx);
|
||||
args.buf_ptr = (__u64)PT_REGS_PARM2(ctx);
|
||||
args.count = (__u64)PT_REGS_PARM3(ctx);
|
||||
|
||||
/* Stocker dans une map hash pour récupération dans l'uretprobe */
|
||||
bpf_map_update_elem(&nginx_read_args_map, &pid_tgid, &args, BPF_ANY);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ============================================================================
|
||||
* uretprobe_read_exit — Fin de read() dans nginx
|
||||
*
|
||||
* Capture les données lues depuis le socket client.
|
||||
* Version simplifiée : capture brute des données, parsing HTTP côté userspace.
|
||||
* ============================================================================
|
||||
*/
|
||||
SEC("uretprobe/read_exit")
|
||||
int uretprobe_read_exit(struct pt_regs *ctx)
|
||||
{
|
||||
__u64 pid_tgid = bpf_get_current_pid_tgid();
|
||||
|
||||
/* Récupérer les arguments sauvegardés */
|
||||
struct nginx_read_args *args = bpf_map_lookup_elem(&nginx_read_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(&nginx_read_args_map, &pid_tgid);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Limiter la capture */
|
||||
__u32 data_len = retval;
|
||||
if (data_len > MAX_NGINX_READ_SIZE)
|
||||
data_len = MAX_NGINX_READ_SIZE;
|
||||
|
||||
/* Buffer PERCPU */
|
||||
__u32 zero = 0;
|
||||
struct nginx_http_event *evt = bpf_map_lookup_elem(&__nginx_buf, &zero);
|
||||
if (!evt) {
|
||||
bpf_map_delete_elem(&nginx_read_args_map, &pid_tgid);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Initialiser l'événement */
|
||||
evt->pid_tgid = pid_tgid;
|
||||
evt->fd = args->fd;
|
||||
evt->src_ip = 0; /* Sera rempli via corrélation TC */
|
||||
evt->src_port = 0;
|
||||
evt->timestamp_ns = bpf_ktime_get_ns();
|
||||
evt->method_len = 0;
|
||||
evt->uri_len = 0;
|
||||
evt->query_len = 0;
|
||||
evt->body_len = 0;
|
||||
evt->data_len = 0;
|
||||
|
||||
/* Copier les données brutes depuis le buffer nginx */
|
||||
if (data_len > 0) {
|
||||
/* Limiter à la taille du champ data (3640 octets) */
|
||||
__u32 copy_len = data_len;
|
||||
if (copy_len > sizeof(evt->data))
|
||||
copy_len = sizeof(evt->data);
|
||||
bpf_probe_read_user(evt->data, copy_len, (void *)args->buf_ptr);
|
||||
evt->data_len = copy_len;
|
||||
}
|
||||
|
||||
/* Émettre l'événement brut vers userspace */
|
||||
/* Le parsing HTTP sera fait côté userspace pour éviter */
|
||||
/* de dépasser la limite d'instructions BPF */
|
||||
bpf_perf_event_output(ctx, &pb_ginx_http, BPF_F_CURRENT_CPU,
|
||||
evt, sizeof(*evt));
|
||||
|
||||
bpf_map_delete_elem(&nginx_read_args_map, &pid_tgid);
|
||||
return 0;
|
||||
}
|
||||
|
||||
char LICENSE[] SEC("license") = "GPL";
|
||||
Reference in New Issue
Block a user