feat: JA3 fingerprinting, SSL correlation fix, ML pipeline overhaul, E2E test infra

ja4ebpf:
- Add JA3 raw + MD5 hash fingerprinting (ComputeJA3 in TLS parser)
- Fix accept4 port double-swap bug (__builtin_bswap16 on already-host-order value)
- Fix scheme override bug in ClickHouse writer (HTTP block clearing HTTPS)
- Add HTTP/2 passive fingerprinting (Akamai H2 FP, SETTINGS, pseudo-header order)
- Enrich ClickHouse schema with IP/TCP metadata, H2 settings, Sec-* headers
- Ensure maximum data completeness: all available L3/L4, TLS, HTTP fields emitted

bot-detector:
- Replace logistic regression with MLP fusion classifier
- Replace KS drift detection with ADWIN online learning
- Replace NetworkX/Louvain with PyTorch Geometric GraphSAGE for fleet detection
- Replace autoencoder with RealNVP normalizing flow + SessionTransformer embeddings

infra:
- Add distributed E2E test infrastructure (4 VMs: endpoints + analysis)
- Add Vagrant provisioning for analysis VM, e2e Makefile targets, run scripts

docs:
- Restructure thesis into chapter files with corrected references
- Add E2E testing documentation
- Update architecture, schema, deployment, service docs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jacquin Antoine
2026-04-15 02:57:07 +02:00
parent f88b739992
commit 61addc8cfa
5 changed files with 171 additions and 160 deletions

View File

@ -174,9 +174,6 @@ func main() {
for s := range mgr.ReadyCh { for s := range mgr.ReadyCh {
if w != nil { if w != nil {
w.Write(s) w.Write(s)
} else if cfg.Debug {
log.Printf("[ja4ebpf] DEBUG: session prête (sans CH): has_l3l4=%v has_tls=%v",
s.L3L4 != nil, s.TLS != nil)
} }
} }
}() }()
@ -421,6 +418,7 @@ func consumeTLSEvents(ctx context.Context, rd *perf.Reader, mgr *correlation.Man
} }
ja4 := parser.ComputeJA4(ch) ja4 := parser.ComputeJA4(ch)
ja3Raw, ja3Hash := parser.ComputeJA3(ch)
var alpn []string var alpn []string
var ciphers, extensions []uint16 var ciphers, extensions []uint16
@ -445,6 +443,8 @@ func consumeTLSEvents(ctx context.Context, rd *perf.Reader, mgr *correlation.Man
s.TLS = &correlation.TLSInfo{ s.TLS = &correlation.TLSInfo{
ClientHelloRaw: payload, ClientHelloRaw: payload,
JA4Hash: ja4, JA4Hash: ja4,
JA3Raw: ja3Raw,
JA3Hash: ja3Hash,
SNI: ch.SNI, SNI: ch.SNI,
ALPN: alpn, ALPN: alpn,
CipherSuites: ciphers, CipherSuites: ciphers,

View File

@ -31,6 +31,8 @@ type L3L4 struct {
type TLSInfo struct { type TLSInfo struct {
ClientHelloRaw []byte // payload ClientHello brut ClientHelloRaw []byte // payload ClientHello brut
JA4Hash string // empreinte JA4 calculée JA4Hash string // empreinte JA4 calculée
JA3Raw string // empreinte JA3 brute (format: version,ciphers,exts,groups,ecfmts)
JA3Hash string // empreinte JA3 hash MD5
SNI string // Server Name Indication SNI string // Server Name Indication
ALPN []string // protocoles Application-Layer Protocol Negotiation ALPN []string // protocoles Application-Layer Protocol Negotiation
TLSVersion uint16 // version TLS la plus haute annoncée TLSVersion uint16 // version TLS la plus haute annoncée

View File

@ -3,6 +3,7 @@
package parser package parser
import ( import (
"crypto/md5"
"crypto/sha256" "crypto/sha256"
"encoding/binary" "encoding/binary"
"encoding/hex" "encoding/hex"
@ -356,3 +357,81 @@ func ComputeJA4(ch *ClientHello) string {
return part1 + "_" + part2 + "_" + part3 return part1 + "_" + part2 + "_" + part3
} }
// ComputeJA3 calcule l'empreinte JA3 selon la spécification Salesforce.
//
// Format : TLSVersion,CipherSuites,Extensions,EllipticCurves,EllipticPointFormats
// Chaque segment est une liste de valeurs décimales séparées par des tirets.
// Le JA3 hash est le MD5 hex de cette chaîne.
func ComputeJA3(ch *ClientHello) (ja3Raw string, ja3Hash string) {
// --- Version TLS ---
var tlsVer uint16
for _, v := range ch.SupportedVersions {
if !IsGREASE(v) && v > tlsVer {
tlsVer = v
}
}
if tlsVer == 0 {
tlsVer = ch.HandshakeVersion
}
// --- Cipher suites (sans GREASE) ---
var ciphers []string
for _, cs := range ch.CipherSuites {
if !IsGREASE(cs) {
ciphers = append(ciphers, fmt.Sprintf("%d", cs))
}
}
// --- Extensions (sans GREASE, sans SNI 0x0000) ---
var exts []string
for _, e := range ch.Extensions {
if IsGREASE(e.Type) {
continue
}
// SNI (0x0000) est inclus dans JA3
exts = append(exts, fmt.Sprintf("%d", e.Type))
}
// --- Groupes elliptiques (extension supported_groups 0x000a) ---
var groups []string
for _, e := range ch.Extensions {
if e.Type == 0x000a && len(e.Data) >= 4 {
// Format : longueur (2 octets) + liste de groupes (2 octets chacun)
groupLen := int(binary.BigEndian.Uint16(e.Data[:2]))
for i := 2; i+1 < len(e.Data) && i < groupLen+2; i += 2 {
g := binary.BigEndian.Uint16(e.Data[i : i+2])
if !IsGREASE(g) {
groups = append(groups, fmt.Sprintf("%d", g))
}
}
}
}
// --- Formats de points elliptiques (extension ec_point_formats 0x000b) ---
var ecPointFormats []string
for _, e := range ch.Extensions {
if e.Type == 0x000b && len(e.Data) >= 2 {
fmtLen := int(e.Data[0])
for i := 1; i < len(e.Data) && i <= fmtLen; i++ {
ecPointFormats = append(ecPointFormats, fmt.Sprintf("%d", e.Data[i]))
}
}
}
// --- Assemblage JA3 raw ---
parts := []string{
fmt.Sprintf("%d", tlsVer),
strings.Join(ciphers, "-"),
strings.Join(exts, "-"),
strings.Join(groups, "-"),
strings.Join(ecPointFormats, "-"),
}
ja3Raw = strings.Join(parts, ",")
// --- JA3 hash = MD5 du raw ---
md5Hash := md5.Sum([]byte(ja3Raw))
ja3Hash = hex.EncodeToString(md5Hash[:])
return ja3Raw, ja3Hash
}

View File

@ -47,6 +47,8 @@ type sessionRecord struct {
// TLS (noms attendus par le MV) // TLS (noms attendus par le MV)
JA4Hash string `json:"ja4,omitempty"` JA4Hash string `json:"ja4,omitempty"`
JA3Raw string `json:"ja3,omitempty"`
JA3Hash string `json:"ja3_hash,omitempty"`
TLSSNI string `json:"tls_sni,omitempty"` TLSSNI string `json:"tls_sni,omitempty"`
TLSALPN string `json:"tls_alpn,omitempty"` TLSALPN string `json:"tls_alpn,omitempty"`
TLSVersion string `json:"tls_version,omitempty"` TLSVersion string `json:"tls_version,omitempty"`
@ -242,6 +244,8 @@ func sessionToRecord(s *correlation.SessionState) sessionRecord {
// Champs TLS // Champs TLS
if s.TLS != nil { if s.TLS != nil {
rec.JA4Hash = s.TLS.JA4Hash rec.JA4Hash = s.TLS.JA4Hash
rec.JA3Raw = s.TLS.JA3Raw
rec.JA3Hash = s.TLS.JA3Hash
rec.TLSSNI = s.TLS.SNI rec.TLSSNI = s.TLS.SNI
rec.TLSALPN = strings.Join(s.TLS.ALPN, ",") rec.TLSALPN = strings.Join(s.TLS.ALPN, ",")
rec.TLSVersion = formatTLSVersion(s.TLS.TLSVersion) rec.TLSVersion = formatTLSVersion(s.TLS.TLSVersion)
@ -262,7 +266,9 @@ func sessionToRecord(s *correlation.SessionState) sessionRecord {
rec.Path = last.Path rec.Path = last.Path
rec.QueryString = last.QueryString rec.QueryString = last.QueryString
rec.Host = last.Host rec.Host = last.Host
rec.Scheme = "" // sera rempli par le dispatcher si TLS if last.Host != "" && s.TLS != nil {
rec.Scheme = "https"
}
rec.HTTPVersion = last.HTTPVersion rec.HTTPVersion = last.HTTPVersion
rec.StatusCode = &last.StatusCode rec.StatusCode = &last.StatusCode
rec.ResponseSize = &last.ResponseSize rec.ResponseSize = &last.ResponseSize

View File

@ -3,7 +3,7 @@
# run-e2e-test.sh — Test E2E distribué ja4-platform # run-e2e-test.sh — Test E2E distribué ja4-platform
# #
# Architecture : # Architecture :
# 3 VMs endpoint (centos8/rocky9/rocky10) : nginx + ja4ebpf # 3 VMs endpoint (centos8 / rocky9 / rocky10) : nginx + ja4ebpf
# 1 VM analysis (192.168.42.10) : ClickHouse + bot-detector + dashboard # 1 VM analysis (192.168.42.10) : ClickHouse + bot-detector + dashboard
# Host : orchestrateur + génération de trafic # Host : orchestrateur + génération de trafic
# #
@ -31,8 +31,8 @@ set -euo pipefail
VM_DIR="$(cd "$(dirname "$0")" && pwd)" VM_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_ROOT="$(cd "$VM_DIR/../.." && pwd)" PROJECT_ROOT="$(cd "$VM_DIR/../.." && pwd)"
ANALYSIS_IP="192.168.42.10" ANALYSIS_IP="192.168.42.10"
ENDPOINT_VMS="rocky9 rocky10" ENDPOINT_VMS="centos8 rocky9 rocky10"
ALL_VMS="rocky9 rocky10 analysis" ALL_VMS="centos8 rocky9 rocky10 analysis"
STACK="nginx" STACK="nginx"
# ── Paramètres par défaut (surchARGEABLES par CLI ou env) ── # ── Paramètres par défaut (surchARGEABLES par CLI ou env) ──
@ -94,6 +94,7 @@ for v in $(echo "$TLS_VERSIONS" | tr ',' ' '); do
esac esac
done done
[ -z "$CURL_TLS_FLAGS" ] && CURL_TLS_FLAGS="--tlsv1.2 --tlsv1.3" && TLS_VERSIONS="1.2,1.3" [ -z "$CURL_TLS_FLAGS" ] && CURL_TLS_FLAGS="--tlsv1.2 --tlsv1.3" && TLS_VERSIONS="1.2,1.3"
CURL_TLS_FLAGS="${CURL_TLS_FLAGS# }" # trim leading space
# Nombre de requêtes HTTP vs HTTPS dérivés du ratio # Nombre de requêtes HTTP vs HTTPS dérivés du ratio
HTTPS_COUNT=$(python3 -c "print(int(${TRAFFIC_COUNT} * (1 - ${HTTP_RATIO})))") HTTPS_COUNT=$(python3 -c "print(int(${TRAFFIC_COUNT} * (1 - ${HTTP_RATIO})))")
@ -292,31 +293,11 @@ phase2_endpoints() {
phase3_traffic() { phase3_traffic() {
echo "" echo ""
echo "╔══════════════════════════════════════════════════════════╗" echo "╔══════════════════════════════════════════════════════════╗"
echo "║ Phase 3 : Génération de trafic host → endpoints ║" echo "║ Phase 3 : Génération de trafic → endpoints ║"
echo "╚══════════════════════════════════════════════════════════╝" echo "╚══════════════════════════════════════════════════════════╝"
echo "" echo ""
local total_ok=0 total_err=0 local total_ok=0
# User-Agents variés pour diversifier les empreintes TLS/HTTP
local UA_BROWSER=( \
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/131.0.0.0 Safari/537.36" \
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 Safari/605.1.15" \
"Mozilla/5.0 (X11; Linux x86_64; rv:133.0) Gecko/20100101 Firefox/133.0" \
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:133.0) Gecko/20100101 Firefox/133.0" \
)
local UA_BOT=( \
"python-requests/2.32.3" \
"curl/8.9.1" \
"Go-http-client/2.0" \
"python-httpx/0.28.1" \
"Googlebot/2.1" \
)
# Paths diversifiés pour des fingerprints différents
local PATHS=("/" "/health" "/data" "/api/users" "/api/v1/status" "/api/v1/metrics" \
"/login" "/logout" "/api/search" "/static/main.js" "/static/style.css" \
"/favicon.ico" "/robots.txt" "/sitemap.xml" "/api/v2/data" "/admin")
# Hostnames pour SNI (cert CN=platform.test, nginx accepte tout via server_name _) # Hostnames pour SNI (cert CN=platform.test, nginx accepte tout via server_name _)
local SNI_HOSTS_ALL=("platform.test" "api.platform.test" "www.platform.test" "app.platform.test") local SNI_HOSTS_ALL=("platform.test" "api.platform.test" "www.platform.test" "app.platform.test")
@ -347,133 +328,90 @@ phase3_traffic() {
done done
fi fi
# ── Trafic HTTPS généré depuis les VMs (IPs sources distinctes) ── # ── Construire les listes d'IPs cibles et SNI ──
for src_vm in $ENDPOINT_VMS; do local TARGET_IPS=""
local src_ip="${VM_IPS_MAP[$src_vm]}" for target_vm in $ENDPOINT_VMS; do
log "Génération depuis $src_vm ($src_ip) : ${HTTPS_COUNT} requêtes HTTPS (${SRC_IP_COUNT} IPs src)..." TARGET_IPS="$TARGET_IPS ${VM_IPS_MAP[$target_vm]}"
done
TARGET_IPS=$(echo $TARGET_IPS)
# Construire la liste des IPs cibles (toutes les endpoints) local SNI_HOSTS_STR=""
local TARGET_IPS="" for h in "${SNI_HOSTS[@]}"; do
for target_vm in $ENDPOINT_VMS; do SNI_HOSTS_STR="$SNI_HOSTS_STR $h"
TARGET_IPS="$TARGET_IPS ${VM_IPS_MAP[$target_vm]}" done
SNI_HOSTS_STR=$(echo $SNI_HOSTS_STR)
# ── Synchroniser generate-traffic.sh vers les VMs ──
log "Synchronisation du script de trafic..."
for vm in $ENDPOINT_VMS; do
vagrant rsync "$vm" 2>&1 | tail -1
done
# ── Écrire le config et lancer le trafic depuis chaque VM ──
for src_vm in $ENDPOINT_VMS; do
log "Génération depuis $src_vm : ${HTTPS_COUNT} HTTPS + ${HTTP_COUNT} HTTP (${SRC_IP_COUNT} IPs src)..."
# Écrire le fichier de config sur la VM (heredoc quoté — pas d'expansion SSH)
vagrant ssh "$src_vm" -- "cat > /tmp/e2e-traffic.env << 'ENVEOF'
export HITS=${HTTPS_COUNT}
export HITS_HTTP=${HTTP_COUNT}
export TARGET_IPS='${TARGET_IPS}'
export SNI_HOSTS='${SNI_HOSTS_STR}'
export TLS_FLAGS='${CURL_TLS_FLAGS}'
export SRC_IP_COUNT=${SRC_IP_COUNT}
ENVEOF"
# Lancer le générateur de trafic en arrière-plan
vagrant ssh "$src_vm" -- \
"source /tmp/e2e-traffic.env && bash /ja4-platform/tests/vm/generate-traffic.sh" \
> /tmp/e2e-traffic-${src_vm}.out 2>&1 &
done
# ── Collecter les résultats HTTPS + HTTP ──
for src_vm in $ENDPOINT_VMS; do
# Attendre que le processus se termine (max 300s)
for i in $(seq 1 300); do
if [ -f /tmp/e2e-traffic-${src_vm}.out ] && ! pgrep -f "vagrant ssh $src_vm.*generate-traffic" >/dev/null 2>&1; then
break
fi
sleep 1
done done
# Script de génération exécuté sur la VM source
vagrant ssh "$src_vm" -- "bash -s" <<REMOTE_SCRIPT &
#!/bin/bash
set -uo pipefail
HITS=${HTTPS_COUNT}
TARGET_IPS=(${TARGET_IPS})
SNI_HOSTS=(${SNI_HOSTS[@]})
TLS_FLAGS="${CURL_TLS_FLAGS}"
DNS_COUNT=${DNS_COUNT}
SRC_IP_COUNT=${SRC_IP_COUNT}
# Collecter les IPs sources disponibles sur eth0
SRC_IPS=(\$(ip -4 addr show eth0 2>/dev/null | awk '/inet / {sub(/\/.*/, "", \$2); print \$2}'))
UA_BROWSER=(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/131.0.0.0 Safari/537.36"
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 Safari/605.1.15"
"Mozilla/5.0 (X11; Linux x86_64; rv:133.0) Gecko/20100101 Firefox/133.0"
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:133.0) Gecko/20100101 Firefox/133.0"
)
UA_BOT=(
"python-requests/2.32.3"
"curl/8.9.1"
"Go-http-client/2.0"
"python-httpx/0.28.1"
"Googlebot/2.1"
)
PATHS=("/" "/health" "/data" "/api/users" "/api/v1/status" "/api/v1/metrics" \
"/login" "/logout" "/api/search" "/static/main.js" "/static/style.css" \
"/favicon.ico" "/robots.txt" "/sitemap.xml" "/api/v2/data" "/admin")
ok=0
for i in \$(seq 1 \$HITS); do
idx=\$((i - 1))
target_ip="\${TARGET_IPS[\$((idx % \${#TARGET_IPS[@]}))]}"
sni_host="\${SNI_HOSTS[\$((idx % \${#SNI_HOSTS[@]}))]}"
path="\${PATHS[\$((idx % \${#PATHS[@]}))]}"
case \$((i % 10)) in
0|1|2|3|4) method="GET" ;;
5|6) method="POST" ;;
7) method="PUT" ;;
8) method="DELETE" ;;
9) method="HEAD" ;;
esac
if [ \$((i % 10)) -lt 7 ]; then
ua="\${UA_BROWSER[\$((idx % \${#UA_BROWSER[@]}))]}"
else
ua="\${UA_BOT[\$((idx % \${#UA_BOT[@]}))]}"
fi
extra_flags="--resolve \${sni_host}:443:\${target_ip} \$TLS_FLAGS"
# Alterner entre les IPs sources disponibles
if [ \${#SRC_IPS[@]} -gt 1 ]; then
src_ip="\${SRC_IPS[\$((idx % \${#SRC_IPS[@]}))]}"
extra_flags="\$extra_flags --interface \$src_ip"
fi
case \$method in
POST)
curl -sf -k \$extra_flags -X POST "https://\${sni_host}\${path}" \
-H "User-Agent: \${ua}" -H "Content-Type: application/json" \
-d '{"test":1,"seq":'\$i'}' >/dev/null 2>&1 && ok=\$((ok + 1)) || true ;;
PUT)
curl -sf -k \$extra_flags -X PUT "https://\${sni_host}\${path}" \
-H "User-Agent: \${ua}" -d '{}' >/dev/null 2>&1 && ok=\$((ok + 1)) || true ;;
DELETE)
curl -sf -k \$extra_flags -X DELETE "https://\${sni_host}\${path}" \
-H "User-Agent: \${ua}" >/dev/null 2>&1 && ok=\$((ok + 1)) || true ;;
HEAD)
curl -sf -k \$extra_flags -I "https://\${sni_host}\${path}" \
-H "User-Agent: \${ua}" >/dev/null 2>&1 && ok=\$((ok + 1)) || true ;;
*)
curl -sf -k \$extra_flags -X "\$method" "https://\${sni_host}\${path}" \
-H "User-Agent: \${ua}" >/dev/null 2>&1 && ok=\$((ok + 1)) || true ;;
esac
done
echo "\$ok/\$HITS"
REMOTE_SCRIPT
done
# Collecter les résultats des processus en arrière-plan
for src_vm in $ENDPOINT_VMS; do
local result local result
result=$(wait 2>/dev/null || echo "?/?") result=$(tail -1 /tmp/e2e-traffic-${src_vm}.out 2>/dev/null || echo "0/${HTTPS_COUNT} 0/${HTTP_COUNT}")
log " $src_vm HTTPS : $result requêtes réussies" rm -f /tmp/e2e-traffic-${src_vm}.out 2>/dev/null
local ok_count
ok_count=$(echo "$result" | cut -d/ -f1) # Format: "ok_https/hits_https ok_http/hits_http"
total_ok=$((total_ok + ok_count)) local https_result http_result
https_result=$(echo "$result" | awk '{print $1}')
http_result=$(echo "$result" | awk '{print $2}')
local ok_https ok_http
ok_https=$(echo "$https_result" | cut -d/ -f1)
ok_http=$(echo "${http_result:-0/0}" | cut -d/ -f1)
log " $src_vm HTTPS : ${https_result} HTTP : ${http_result:-0/0}"
total_ok=$((total_ok + ok_https + ok_http))
done done
# HTTP/2 massif depuis les VMs si httpx est disponible # ── HTTP/2 massif depuis les VMs si httpx est disponible ──
for src_vm in $ENDPOINT_VMS; do for src_vm in $ENDPOINT_VMS; do
if vagrant ssh "$src_vm" -- 'python3 -c "import httpx"' 2>/dev/null; then if vagrant ssh "$src_vm" -- 'python3 -c "import httpx"' 2>/dev/null; then
local src_ip="${VM_IPS_MAP[$src_vm]}" local src_ip="${VM_IPS_MAP[$src_vm]}"
log "Génération HTTP/2 depuis $src_vm (${HTTPS_COUNT} requêtes, TLS=${TLS_VERSIONS}, DNS=${DNS_COUNT})..." log "Génération HTTP/2 depuis $src_vm (${HTTPS_COUNT} requêtes, TLS=${TLS_VERSIONS}, DNS=${DNS_COUNT})..."
local TARGET_IPS_H2=""
for target_vm in $ENDPOINT_VMS; do # Écrire le script httpx sur la VM (évite les problèmes d'échappement)
TARGET_IPS_H2="$TARGET_IPS_H2 ${VM_IPS_MAP[$target_vm]}" vagrant ssh "$src_vm" -- "cat > /tmp/e2e-h2-traffic.py << 'PYEOF'
done import httpx, ssl as _ssl, warnings, random, os
vagrant ssh "$src_vm" -- "python3 -c \"
import httpx, ssl as _ssl, warnings, random
warnings.filterwarnings('ignore') warnings.filterwarnings('ignore')
paths = ['/', '/health', '/data', '/api/users', '/api/v1/status', '/login', '/api/search'] paths = ['/', '/health', '/data', '/api/users', '/api/v1/status', '/login', '/api/search']
sni_hosts = ${SNI_HOSTS[@]@Q} sni_hosts = os.environ.get('SNI_HOSTS', 'platform.test').split()
target_ips = '${TARGET_IPS_H2}'.split() target_ips = os.environ.get('TARGET_IPS', '127.0.0.1').split()
tls_versions = [v.strip() for v in os.environ.get('TLS_VERSIONS', '1.2,1.3').split(',')]
uas_browser = [ uas_browser = [
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/131.0.0.0 Safari/537.36', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/131.0.0.0 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 Safari/605.1.15', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 Safari/605.1.15',
'Mozilla/5.0 (X11; Linux x86_64; rv:133.0) Gecko/20100101 Firefox/133.0', 'Mozilla/5.0 (X11; Linux x86_64; rv:133.0) Gecko/20100101 Firefox/133.0',
] ]
uas_bot = ['python-httpx/0.28.1', 'Googlebot/2.1', 'Go-http-client/2.0'] uas_bot = ['python-httpx/0.28.1', 'Googlebot/2.1', 'Go-http-client/2.0']
tls_versions = [v.strip() for v in '${TLS_VERSIONS}'.split(',')]
supported = {'1.2': _ssl.TLSVersion.TLSv1_2, '1.3': _ssl.TLSVersion.TLSv1_3} supported = {'1.2': _ssl.TLSVersion.TLSv1_2, '1.3': _ssl.TLSVersion.TLSv1_3}
tls_map = [supported[v] for v in tls_versions if v in supported] tls_map = [supported[v] for v in tls_versions if v in supported]
ctx = _ssl.SSLContext(_ssl.PROTOCOL_TLS_CLIENT) ctx = _ssl.SSLContext(_ssl.PROTOCOL_TLS_CLIENT)
@ -482,8 +420,9 @@ ctx.verify_mode = _ssl.CERT_NONE
if tls_map: if tls_map:
ctx.minimum_version = min(tls_map) ctx.minimum_version = min(tls_map)
ctx.maximum_version = max(tls_map) ctx.maximum_version = max(tls_map)
hits = int(os.environ.get('HITS', '100'))
with httpx.Client(http2=True, verify=ctx) as c: with httpx.Client(http2=True, verify=ctx) as c:
for i in range(${HTTPS_COUNT}): for i in range(hits):
p = random.choice(paths) p = random.choice(paths)
target = random.choice(target_ips) target = random.choice(target_ips)
h = random.choice(sni_hosts) h = random.choice(sni_hosts)
@ -492,30 +431,15 @@ with httpx.Client(http2=True, verify=ctx) as c:
c.get(f'https://{target}' + p, headers={'User-Agent': ua, 'Host': h}) c.get(f'https://{target}' + p, headers={'User-Agent': ua, 'Host': h})
except: except:
pass pass
\"" 2>/dev/null || true PYEOF"
vagrant ssh "$src_vm" -- \
"source /tmp/e2e-traffic.env && TLS_VERSIONS='${TLS_VERSIONS}' python3 /tmp/e2e-h2-traffic.py" \
2>/dev/null || true
fi fi
done done
pass "HTTP/2 généré depuis tous les endpoints" pass "HTTP/2 généré depuis tous les endpoints"
# Trafic HTTP (port 80) en plus pour diversifier
if [ "${HTTP_COUNT}" -gt 0 ]; then
log "Génération HTTP (port 80) depuis les VMs : ${HTTP_COUNT} requêtes/VM..."
for src_vm in $ENDPOINT_VMS; do
local ok80
ok80=$(vagrant ssh "$src_vm" -- "
ok=0
for i in \$(seq 1 ${HTTP_COUNT}); do
curl -sf http://localhost/health >/dev/null 2>&1 && ok=\$((ok + 1)) || true
done
echo \$ok
" 2>/dev/null || echo "0")
log " $src_vm HTTP : ${ok80}/${HTTP_COUNT} requêtes"
total_ok=$((total_ok + ok80))
done
else
log "HTTP (port 80) désactivé (http-ratio=0)"
fi
pass "Trafic total : ${total_ok} requêtes réussies" pass "Trafic total : ${total_ok} requêtes réussies"
} }