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:
84
services/ja4ebpf/internal/dispatcher/dispatcher.go
Normal file
84
services/ja4ebpf/internal/dispatcher/dispatcher.go
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user