Files
ja4-platform/services/ja4ebpf/bpf/uprobe_apache.c
Jacquin Antoine 4d30d9a7cb 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 <noreply@anthropic.com>
2026-04-20 18:22:10 +02:00

106 lines
2.7 KiB
C

/* uprobe_apache.c — Capture HTTP depuis Apache httpd via recvfrom
*
* Identique à nginx : sys_enter_recvfrom + kretprobe __x64_sys_recvfrom
*
* ============================================================================
*/
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include "bpf_types.h"
#define MAX_RECV_SIZE 4096
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;
__u8 *enabled = bpf_map_lookup_elem(&apache_http_pid_map, &pid);
if (!enabled || *enabled == 0) {
return 0;
}
struct recvfrom_args *args = bpf_map_lookup_elem(&apache_http_recv_args_map, &pid_tgid);
if (!args) {
return 0;
}
long retval = PT_REGS_RC(ctx);
if (retval <= 0) {
bpf_map_delete_elem(&apache_http_recv_args_map, &pid_tgid);
return 0;
}
__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;
}
e->pid_tgid = pid_tgid;
e->fd = args->sockfd;
e->src_ip = 0;
e->src_port = 0;
e->timestamp_ns = bpf_ktime_get_ns();
e->method_len = 0;
e->uri_len = 0;
e->query_len = 0;
e->body_len = 0;
e->data_len = 0;
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;
}
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;
}
char LICENSE[] SEC("license") = "GPL";