feat: add ja4ebpf service — eBPF-based TLS/TCP fingerprinting daemon

- TC ingress hook captures TCP SYN (L3/L4) and TLS ClientHello
- Uprobes on SSL_read/SSL_set_fd capture decrypted TLS data
- Kprobes on accept4 correlate socket FDs to client IP:port
- JA4 fingerprint computed from parsed TLS ClientHello
- HTTP/2 SETTINGS and WINDOW_UPDATE extracted from decrypted streams
- Session manager with sharded map (256 shards) and GC goroutine
- Slowloris detection: sessions with no requests after 10s threshold
- ClickHouse batch writer to ja4_logs.http_logs_raw (raw_json)
- All tests pass: 17 parser + 10 correlation tests

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
toto
2026-04-11 22:43:26 +02:00
parent 7eb3ad21fd
commit a1e4c1dad5
24 changed files with 3984 additions and 0 deletions

View File

@ -0,0 +1,84 @@
// Package dispatcher fournit le routeur Magic Bytes qui unifie les événements
// issus des RingBuffers SSL (trafic chiffré déchiffré par uprobe) et HTTP plain
// (trafic clair capturé par TC ingress), et les route vers le bon parser L7.
package dispatcher
import (
"bytes"
)
// --- Marqueurs Magic Bytes -----------------------------------------------
// h2MagicBytes est la préface HTTP/2 client sous forme de []byte.
var h2MagicBytes = []byte("PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n")
// Préfixes de méthodes HTTP/1.x courants (liste non exhaustive).
var http1Prefixes = [][]byte{
[]byte("GET "),
[]byte("POST "),
[]byte("PUT "),
[]byte("DELETE "),
[]byte("HEAD "),
[]byte("OPTIONS "),
[]byte("PATCH "),
[]byte("CONNECT "),
[]byte("TRACE "),
}
// Protocol identifie le protocole applicatif détecté à partir des premiers octets.
type Protocol uint8
const (
ProtoUnknown Protocol = iota
ProtoHTTP1 // HTTP/1.0 ou HTTP/1.1
ProtoHTTP2 // HTTP/2 (préface "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n")
)
// RawEvent représente un buffer brut reçu depuis un RingBuffer eBPF
// (SSL_read déchiffré OU payload TCP HTTP en clair).
type RawEvent struct {
SrcIP uint32
SrcPort uint16
PID uint32 // 0 pour les événements TC (HTTP plain, pas de PID)
FD uint32 // 0 pour les événements TC
Data []byte
EOF bool // connexion terminée (uprobe SSL uniquement)
Cleartext bool // true = provient de rb_http_plain (TC), false = uprobe SSL
}
// Classify inspecte les premiers octets du buffer et retourne le Protocol détecté.
// La détection est purement basée sur les octets de début (pas de parsing complet).
func Classify(data []byte) Protocol {
if len(data) == 0 {
return ProtoUnknown
}
// Détection HTTP/2 : préface exacte de 24 octets
if bytes.HasPrefix(data, h2MagicBytes) {
return ProtoHTTP2
}
// Détection HTTP/1.x : commence par une méthode connue suivie d'un espace
for _, pfx := range http1Prefixes {
if bytes.HasPrefix(data, pfx) {
return ProtoHTTP1
}
}
// Détection HTTP/2 partiel : peut arriver si le magic est fragmenté
// sur plusieurs lectures. Dans ce cas on laisse l'appelant accumuler.
n := minInt(len(data), len(h2MagicBytes))
if bytes.HasPrefix(h2MagicBytes, data[:n]) && len(data) < len(h2MagicBytes) {
return ProtoHTTP2 // préface partielle — traiter comme H2 en cours
}
return ProtoUnknown
}
// minInt retourne le minimum de deux entiers (helper interne).
func minInt(a, b int) int {
if a < b {
return a
}
return b
}