Files
ja4-platform/services/ja4ebpf/bpf/uprobe_nginx.c
Jacquin Antoine 9e4bfe8289 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>
2026-04-20 01:29:53 +02:00

111 lines
3.6 KiB
C

/* 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";