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:
Jacquin Antoine
2026-04-15 00:09:32 +02:00
parent 7894d39f1c
commit f88b739992
40 changed files with 2154 additions and 337 deletions

View File

@ -315,7 +315,9 @@ func consumeSynEvents(ctx context.Context, rd *perf.Reader, mgr *correlation.Man
// src_ip et src_port stockés en host byte order (bpf_ntohl/bpf_ntohs dans BPF C).
srcIPRaw := binary.LittleEndian.Uint32(data[0:4])
dstIPRaw := binary.LittleEndian.Uint32(data[4:8])
srcPort := binary.LittleEndian.Uint16(data[8:10])
dstPort := binary.LittleEndian.Uint16(data[10:12])
var key correlation.SessionKey
key.SrcIP[0] = byte(srcIPRaw >> 24)
@ -324,6 +326,12 @@ func consumeSynEvents(ctx context.Context, rd *perf.Reader, mgr *correlation.Man
key.SrcIP[3] = byte(srcIPRaw)
key.SrcPort = srcPort
var dstIP [4]byte
dstIP[0] = byte(dstIPRaw >> 24)
dstIP[1] = byte(dstIPRaw >> 16)
dstIP[2] = byte(dstIPRaw >> 8)
dstIP[3] = byte(dstIPRaw)
// Champs IP/TCP aux offsets corrects (dst_ip occupe les octets 4-7)
ttl := data[12]
dfBit := data[13] != 0
@ -342,6 +350,8 @@ func consumeSynEvents(ctx context.Context, rd *perf.Reader, mgr *correlation.Man
mgr.Update(key, func(s *correlation.SessionState) {
s.L3L4 = &correlation.L3L4{
DstIP: dstIP,
DstPort: dstPort,
TTL: ttl,
DFBit: dfBit,
IPID: ipID,
@ -420,6 +430,17 @@ func consumeTLSEvents(ctx context.Context, rd *perf.Reader, mgr *correlation.Man
ciphers = ch.CipherSuites
alpn = ch.ALPN
// Déterminer la version TLS la plus haute (comme ComputeJA4)
var tlsVer uint16
for _, v := range ch.SupportedVersions {
if !parser.IsGREASE(v) && v > tlsVer {
tlsVer = v
}
}
if tlsVer == 0 {
tlsVer = ch.HandshakeVersion
}
mgr.Update(key, func(s *correlation.SessionState) {
s.TLS = &correlation.TLSInfo{
ClientHelloRaw: payload,
@ -428,6 +449,7 @@ func consumeTLSEvents(ctx context.Context, rd *perf.Reader, mgr *correlation.Man
ALPN: alpn,
CipherSuites: ciphers,
Extensions: extensions,
TLSVersion: tlsVer,
Timestamp: time.Now(),
}
// Corréler si L3/L4 est déjà présent
@ -540,6 +562,7 @@ func consumeSSLEvents(ctx context.Context, rd *perf.Reader, mgr *correlation.Man
}
}
if len(s.Requests) == 0 {
req.HTTPVersion = "HTTP/2"
s.Requests = append(s.Requests, req)
}
_ = s.TLS // corrélation implicite
@ -559,8 +582,11 @@ func consumeSSLEvents(ctx context.Context, rd *perf.Reader, mgr *correlation.Man
Method: req.Method,
Path: req.Path,
QueryString: req.Query,
Host: req.HeaderKV["Host"],
HeaderOrder: req.Headers,
HeaderOrderSig: req.HeaderSig,
HeaderKV: req.HeaderKV,
HTTPVersion: req.Protocol,
})
_ = s.TLS // corrélation implicite
})
@ -699,8 +725,11 @@ func consumeHTTPPlainEvents(ctx context.Context, rd *perf.Reader, mgr *correlati
Method: req.Method,
Path: req.Path,
QueryString: req.Query,
Host: req.HeaderKV["Host"],
HeaderOrder: req.Headers,
HeaderOrderSig: req.HeaderSig,
HeaderKV: req.HeaderKV,
HTTPVersion: req.Protocol,
})
// Corréler si L3/L4 est déjà présent (TCP SYN capturé)
_ = s.L3L4 // corrélation implicite