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:
@ -19,6 +19,7 @@ import (
|
||||
|
||||
//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -cc clang -target amd64 -cflags "-O2 -g -Wall -D__TARGET_ARCH_x86 -Wno-pass-failed" Ja4Tc ../../bpf/tc_capture.c -- -I../../bpf/headers
|
||||
//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -cc clang -target amd64 -cflags "-O2 -g -Wall -D__TARGET_ARCH_x86 -Wno-pass-failed" Ja4Ssl ../../bpf/uprobe_ssl.c -- -I../../bpf/headers
|
||||
//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -cc clang -target amd64 -cflags "-O2 -g -Wall -D__TARGET_ARCH_x86 -Wno-pass-failed" Ja4Nginx ../../bpf/uprobe_nginx.c -- -I../../bpf/headers
|
||||
|
||||
// perCPUBufferSize est la taille du buffer perf per-CPU en octets (256 KB).
|
||||
const perCPUBufferSize = 256 * 1024
|
||||
@ -28,6 +29,7 @@ const perCPUBufferSize = 256 * 1024
|
||||
type Loader struct {
|
||||
tcObjs *Ja4TcObjects // généré par bpf2go (tc_capture.c)
|
||||
sslObjs *Ja4SslObjects // généré par bpf2go (uprobe_ssl.c)
|
||||
nginxObjs *Ja4NginxObjects // généré par bpf2go (uprobe_nginx.c)
|
||||
tcLinks []netlink.Link // interfaces netlink pour cleanup TC
|
||||
uprobeLinks []link.Link
|
||||
statsMap *ebpf.Map // map tc_stats pour lecture des compteurs BPF (mode debug)
|
||||
@ -44,6 +46,8 @@ type Loader struct {
|
||||
AcceptReader *perf.Reader
|
||||
// HTTPPlainReader lit les payloads HTTP en clair depuis pb_http_plain.
|
||||
HTTPPlainReader *perf.Reader
|
||||
// NginxHTTPReader lit les requêtes HTTP complètes depuis nginx via uprobes.
|
||||
NginxHTTPReader *perf.Reader
|
||||
}
|
||||
|
||||
// StatNames associe chaque index de compteur BPF à un nom lisible.
|
||||
@ -147,6 +151,14 @@ func New() (*Loader, error) {
|
||||
return nil, fmt.Errorf("chargement objets SSL eBPF: %w", err)
|
||||
}
|
||||
|
||||
// Charger les objets nginx/uprobe (uprobe_nginx.c)
|
||||
nginxObjs := &Ja4NginxObjects{}
|
||||
if err := LoadJa4NginxObjects(nginxObjs, nil); err != nil {
|
||||
sslObjs.Close()
|
||||
tcObjs.Close()
|
||||
return nil, fmt.Errorf("chargement objets nginx eBPF: %w", err)
|
||||
}
|
||||
|
||||
// Initialiser les readers pour chaque perf event array
|
||||
synReader, err := perf.NewReader(tcObjs.PbTcpSyn, perCPUBufferSize)
|
||||
if err != nil {
|
||||
@ -193,9 +205,22 @@ func New() (*Loader, error) {
|
||||
return nil, fmt.Errorf("création reader pb_accept: %w", err)
|
||||
}
|
||||
|
||||
nginxHTTPReader, err := perf.NewReader(nginxObjs.PbGinxHttp, perCPUBufferSize)
|
||||
if err != nil {
|
||||
nginxHTTPReader.Close()
|
||||
acceptReader.Close()
|
||||
sslReader.Close()
|
||||
httpPlainReader.Close()
|
||||
tlsReader.Close()
|
||||
synReader.Close()
|
||||
sslObjs.Close()
|
||||
return nil, fmt.Errorf("création reader pb_ginx_http: %w", err)
|
||||
}
|
||||
|
||||
return &Loader{
|
||||
tcObjs: tcObjs,
|
||||
sslObjs: sslObjs,
|
||||
nginxObjs: nginxObjs,
|
||||
statsMap: tcObjs.TcStats,
|
||||
allowedPorts: tcObjs.AllowedPorts,
|
||||
ignoredSrc: tcObjs.IgnoredSrc,
|
||||
@ -204,6 +229,7 @@ func New() (*Loader, error) {
|
||||
SSLReader: sslReader,
|
||||
AcceptReader: acceptReader,
|
||||
HTTPPlainReader: httpPlainReader,
|
||||
NginxHTTPReader: nginxHTTPReader,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -343,6 +369,36 @@ func (l *Loader) AttachAcceptProbe() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// AttachUprobesNginx attache les uprobes read() dans nginx pour capturer
|
||||
// le trafic HTTP complet. Cette approche utilise read() syscall qui est
|
||||
// appelé par nginx pour lire les requêtes depuis les clients.
|
||||
func (l *Loader) AttachUprobesNginx(nginxBinPath string) error {
|
||||
if _, err := os.Stat(nginxBinPath); err != nil {
|
||||
return fmt.Errorf("binaire nginx %q: %w", nginxBinPath, err)
|
||||
}
|
||||
|
||||
ex, err := link.OpenExecutable(nginxBinPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ouverture exécutable %q pour uprobe: %w", nginxBinPath, err)
|
||||
}
|
||||
|
||||
// Attacher uprobe sur read() (entrée)
|
||||
readEntryLink, err := ex.Uprobe("read", l.nginxObjs.UprobeReadEntry, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("attachement uprobe read (entry): %w", err)
|
||||
}
|
||||
l.uprobeLinks = append(l.uprobeLinks, readEntryLink)
|
||||
|
||||
// Attacher uretprobe sur read() (sortie) pour capturer les données lues
|
||||
readExitLink, err := ex.Uretprobe("read", l.nginxObjs.UretprobeReadExit, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("attachement uretprobe read (exit): %w", err)
|
||||
}
|
||||
l.uprobeLinks = append(l.uprobeLinks, readExitLink)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// attachSSLWrite attache les uprobes SSL_write pour capturer
|
||||
// les réponses HTTP du serveur (direction=1).
|
||||
func (l *Loader) attachSSLWrite(ex *link.Executable) error {
|
||||
@ -378,6 +434,9 @@ func (l *Loader) Close() error {
|
||||
if l.SynReader != nil {
|
||||
l.SynReader.Close()
|
||||
}
|
||||
if l.NginxHTTPReader != nil {
|
||||
l.NginxHTTPReader.Close()
|
||||
}
|
||||
|
||||
// Détacher les filtres TC ingress sur toutes les interfaces
|
||||
for _, nlLink := range l.tcLinks {
|
||||
|
||||
Reference in New Issue
Block a user