- 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>
85 lines
2.5 KiB
Go
85 lines
2.5 KiB
Go
// 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
|
|
}
|