feat(ja4ebpf): add SSL_write uprobe, HPACK decoder, and AcceptCache for session correlation

Add uprobe_ssl_write_entry/uretprobe_ssl_write_exit to capture server HTTP
responses via SSL_write with direction=1. Implement full HPACK decoder
(RFC 7541 static table, multi-byte integers, literal representations) for
HTTP/2 header extraction. Add AcceptCache mapping {tgid,fd}→SessionKey
from accept4 events as authoritative source for SSL correlation when BPF
ssl_conn_map has src_ip=0. Add ip_total_length to tcp_syn_event BPF struct.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jacquin Antoine
2026-04-15 03:34:43 +02:00
parent a02423fd18
commit 24306ef390
7 changed files with 847 additions and 16 deletions

View File

@ -265,6 +265,12 @@ func (l *Loader) AttachUprobes(sslLibPath string) error {
}
l.uprobeLinks = append(l.uprobeLinks, readExitLink)
// SSL_write — capture les réponses HTTP du serveur (direction=1)
// Les programmes BPF uprobe/SSL_write et uretprobe/SSL_write sont
// chargés depuis les objets Ja4Ssl. Si les objets BPF n'ont pas été
// régénérés (pas de clang sur le host), ces programmes sont absents.
_ = l.attachSSLWrite(ex)
return nil
}
@ -287,6 +293,50 @@ func (l *Loader) AttachAcceptProbe() error {
return nil
}
// attachSSLWrite tente d'attacher les uprobes SSL_write pour capturer
// les réponses HTTP du serveur. Si les programmes BPF SSL_write ne sont
// pas disponibles (objets non régénérés), retourne nil sans bloquer.
func (l *Loader) attachSSLWrite(ex *link.Executable) error {
// Charger la collection spec embarquée pour vérifier si SSL_write existe
spec, err := LoadJa4Ssl()
if err != nil {
return nil
}
entrySpec, hasEntry := spec.Programs["uprobe_ssl_write_entry"]
exitSpec, hasExit := spec.Programs["uretprobe_ssl_write_exit"]
if !hasEntry || !hasExit {
return nil // programmes SSL_write absents — BPF non régénéré
}
writeEntry, err := ebpf.NewProgram(entrySpec)
if err != nil {
return nil
}
writeExit, err := ebpf.NewProgram(exitSpec)
if err != nil {
writeEntry.Close()
return nil
}
entryLink, err := ex.Uprobe("SSL_write", writeEntry, nil)
if err != nil {
writeEntry.Close()
writeExit.Close()
return fmt.Errorf("attachement uprobe SSL_write (entry): %w", err)
}
l.uprobeLinks = append(l.uprobeLinks, entryLink)
exitLink, err := ex.Uretprobe("SSL_write", writeExit, nil)
if err != nil {
writeExit.Close()
return fmt.Errorf("attachement uretprobe SSL_write (exit): %w", err)
}
l.uprobeLinks = append(l.uprobeLinks, exitLink)
return nil
}
// Close détache tous les hooks eBPF et libère toutes les ressources associées.
func (l *Loader) Close() error {
if l.HTTPPlainReader != nil {