feat(e2e): add distributed E2E test framework with parametric traffic generation
Add run-e2e-test.sh with CLI parameters (--hits, --http-ratio, --dns, --tls, --src-ips, --keep-analysis, --up) for configurable traffic generation. Traffic runs from VM endpoints with multiple source IPs (alias IPs on eth0) to produce distinct sessions for the ML pipeline. Fix curl TLS flags (--tlsv1.2 instead of --tls-v1-2), skip redundant local verification in distributed mode, and fix dashboard is_available() cache that never retried after ClickHouse recovery. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@ -8,12 +8,13 @@ import (
|
||||
|
||||
// HTTP1Request représente une requête HTTP/1.x parsée depuis le flux déchiffré.
|
||||
type HTTP1Request struct {
|
||||
Method string // méthode HTTP (GET, POST, …)
|
||||
Path string // chemin (sans query string)
|
||||
Query string // query string (sans le '?')
|
||||
Protocol string // "HTTP/1.0" ou "HTTP/1.1"
|
||||
Headers []string // noms des en-têtes dans l'ordre exact d'arrivée
|
||||
HeaderSig string // signature : noms joints par ";"
|
||||
Method string // méthode HTTP (GET, POST, …)
|
||||
Path string // chemin (sans query string)
|
||||
Query string // query string (sans le '?')
|
||||
Protocol string // "HTTP/1.0" ou "HTTP/1.1"
|
||||
Headers []string // noms des en-têtes dans l'ordre exact d'arrivée
|
||||
HeaderSig string // signature : noms joints par ";"
|
||||
HeaderKV map[string]string // valeurs des en-têtes clés (Host, User-Agent, etc.)
|
||||
}
|
||||
|
||||
// HTTP1Response représente le début d'une réponse HTTP/1.x (status line).
|
||||
@ -27,6 +28,14 @@ var knownMethods = []string{
|
||||
"OPTIONS", "PATCH", "CONNECT", "TRACE",
|
||||
}
|
||||
|
||||
// capturedHeaders est la liste des en-têtes dont on capture la valeur.
|
||||
var capturedHeaders = []string{
|
||||
"Host", "User-Agent", "Accept", "Accept-Encoding", "Accept-Language",
|
||||
"Content-Type", "X-Request-Id", "X-Trace-Id", "X-Forwarded-For",
|
||||
"Sec-CH-UA", "Sec-CH-UA-Mobile", "Sec-CH-UA-Platform",
|
||||
"Sec-Fetch-Dest", "Sec-Fetch-Mode", "Sec-Fetch-Site",
|
||||
}
|
||||
|
||||
// IsHTTP1Request retourne true si les premiers octets ressemblent à une
|
||||
// requête HTTP/1.x (commence par une méthode reconnue suivi d'un espace).
|
||||
func IsHTTP1Request(data []byte) bool {
|
||||
@ -91,8 +100,9 @@ func ParseHTTP1Request(data []byte) *HTTP1Request {
|
||||
query = rawPath[idx+1:]
|
||||
}
|
||||
|
||||
// Extraire les noms d'en-têtes dans l'ordre
|
||||
// Extraire les noms d'en-têtes dans l'ordre + capturer les valeurs clés
|
||||
headers := make([]string, 0, len(lines)-1)
|
||||
headerKV := make(map[string]string, len(capturedHeaders))
|
||||
for _, line := range lines[1:] {
|
||||
if line == "" {
|
||||
break
|
||||
@ -101,6 +111,13 @@ func ParseHTTP1Request(data []byte) *HTTP1Request {
|
||||
name := strings.TrimSpace(line[:colon])
|
||||
if name != "" {
|
||||
headers = append(headers, name)
|
||||
// Capturer la valeur si c'est un header d'intérêt
|
||||
for _, key := range capturedHeaders {
|
||||
if strings.EqualFold(name, key) {
|
||||
headerKV[key] = strings.TrimSpace(line[colon+1:])
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -114,6 +131,7 @@ func ParseHTTP1Request(data []byte) *HTTP1Request {
|
||||
Protocol: protocol,
|
||||
Headers: headers,
|
||||
HeaderSig: sig,
|
||||
HeaderKV: headerKV,
|
||||
}
|
||||
}
|
||||
|
||||
@ -143,4 +161,4 @@ func ParseHTTP1Response(data []byte) *HTTP1Response {
|
||||
return nil
|
||||
}
|
||||
return &HTTP1Response{StatusCode: code}
|
||||
}
|
||||
}
|
||||
@ -247,9 +247,9 @@ func parseSupportedVersions(data []byte) []uint16 {
|
||||
return versions
|
||||
}
|
||||
|
||||
// isGREASE vérifie si une valeur est une valeur GREASE (RFC 8701).
|
||||
// IsGREASE vérifie si une valeur est une valeur GREASE (RFC 8701).
|
||||
// Les valeurs GREASE suivent le motif 0x?A?A (ex: 0x0A0A, 0x1A1A, ...).
|
||||
func isGREASE(v uint16) bool {
|
||||
func IsGREASE(v uint16) bool {
|
||||
return v&0x0F0F == 0x0A0A && v>>8 == v&0xFF
|
||||
}
|
||||
|
||||
@ -279,7 +279,7 @@ func ComputeJA4(ch *ClientHello) string {
|
||||
// --- Version TLS : version la plus haute annoncée ---
|
||||
var tlsVer uint16
|
||||
for _, v := range ch.SupportedVersions {
|
||||
if !isGREASE(v) && v > tlsVer {
|
||||
if !IsGREASE(v) && v > tlsVer {
|
||||
tlsVer = v
|
||||
}
|
||||
}
|
||||
@ -298,7 +298,7 @@ func ComputeJA4(ch *ClientHello) string {
|
||||
// --- Comptage des cipher suites (sans GREASE) ---
|
||||
var ciphers []uint16
|
||||
for _, cs := range ch.CipherSuites {
|
||||
if !isGREASE(cs) {
|
||||
if !IsGREASE(cs) {
|
||||
ciphers = append(ciphers, cs)
|
||||
}
|
||||
}
|
||||
@ -307,7 +307,7 @@ func ComputeJA4(ch *ClientHello) string {
|
||||
// --- Comptage des extensions (sans GREASE, sans SNI 0x0000) ---
|
||||
var extensions []uint16
|
||||
for _, ext := range ch.Extensions {
|
||||
if isGREASE(ext.Type) {
|
||||
if IsGREASE(ext.Type) {
|
||||
continue
|
||||
}
|
||||
if ext.Type == 0x0000 { // SNI exclue du comptage
|
||||
|
||||
Reference in New Issue
Block a user