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>
164 lines
4.4 KiB
Go
164 lines
4.4 KiB
Go
// Package parser fournit les parseurs pour les protocoles HTTP/1.x, HTTP/2 et TLS.
|
|
package parser
|
|
|
|
import (
|
|
"bytes"
|
|
"strings"
|
|
)
|
|
|
|
// 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 ";"
|
|
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).
|
|
type HTTP1Response struct {
|
|
StatusCode int
|
|
}
|
|
|
|
// knownMethods est la liste des méthodes HTTP/1.x reconnues.
|
|
var knownMethods = []string{
|
|
"GET", "POST", "PUT", "DELETE", "HEAD",
|
|
"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 {
|
|
for _, m := range knownMethods {
|
|
if bytes.HasPrefix(data, []byte(m+" ")) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// IsHTTP1Response retourne true si les premiers octets ressemblent à une
|
|
// réponse HTTP/1.x ("HTTP/1.").
|
|
func IsHTTP1Response(data []byte) bool {
|
|
return bytes.HasPrefix(data, []byte("HTTP/1."))
|
|
}
|
|
|
|
// ParseHTTP1Request extrait les champs d'une requête HTTP/1.x depuis un buffer brut.
|
|
// Retourne nil sans erreur si le buffer ne contient pas de requête complète.
|
|
func ParseHTTP1Request(data []byte) *HTTP1Request {
|
|
// Localiser la fin de la request-line + headers (double CRLF)
|
|
headerEnd := bytes.Index(data, []byte("\r\n\r\n"))
|
|
if headerEnd < 0 {
|
|
headerEnd = len(data)
|
|
}
|
|
text := string(data[:headerEnd])
|
|
lines := strings.Split(text, "\r\n")
|
|
if len(lines) == 0 {
|
|
return nil
|
|
}
|
|
|
|
// Parser la request-line : "METHOD path HTTP/x.y"
|
|
requestLine := lines[0]
|
|
parts := strings.SplitN(requestLine, " ", 3)
|
|
if len(parts) < 2 {
|
|
return nil
|
|
}
|
|
method := parts[0]
|
|
rawPath := parts[1]
|
|
protocol := "HTTP/1.1"
|
|
if len(parts) == 3 {
|
|
protocol = parts[2]
|
|
}
|
|
|
|
// Valider la méthode
|
|
validMethod := false
|
|
for _, m := range knownMethods {
|
|
if method == m {
|
|
validMethod = true
|
|
break
|
|
}
|
|
}
|
|
if !validMethod {
|
|
return nil
|
|
}
|
|
|
|
// Séparer path et query string
|
|
path := rawPath
|
|
query := ""
|
|
if idx := strings.Index(rawPath, "?"); idx >= 0 {
|
|
path = rawPath[:idx]
|
|
query = rawPath[idx+1:]
|
|
}
|
|
|
|
// 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
|
|
}
|
|
if colon := strings.Index(line, ":"); colon > 0 {
|
|
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
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
sig := strings.Join(headers, ";")
|
|
|
|
return &HTTP1Request{
|
|
Method: method,
|
|
Path: path,
|
|
Query: query,
|
|
Protocol: protocol,
|
|
Headers: headers,
|
|
HeaderSig: sig,
|
|
HeaderKV: headerKV,
|
|
}
|
|
}
|
|
|
|
// ParseHTTP1Response extrait le code de statut d'une réponse HTTP/1.x.
|
|
// Retourne nil si le buffer n'est pas une réponse HTTP/1.x reconnaissable.
|
|
func ParseHTTP1Response(data []byte) *HTTP1Response {
|
|
if !IsHTTP1Response(data) {
|
|
return nil
|
|
}
|
|
// Status-line : "HTTP/1.1 200 OK\r\n..."
|
|
line := data
|
|
if idx := bytes.IndexByte(data, '\n'); idx >= 0 {
|
|
line = data[:idx]
|
|
}
|
|
parts := strings.SplitN(strings.TrimRight(string(line), "\r\n"), " ", 3)
|
|
if len(parts) < 2 {
|
|
return nil
|
|
}
|
|
code := 0
|
|
for _, c := range parts[1] {
|
|
if c < '0' || c > '9' {
|
|
break
|
|
}
|
|
code = code*10 + int(c-'0')
|
|
}
|
|
if code < 100 || code > 599 {
|
|
return nil
|
|
}
|
|
return &HTTP1Response{StatusCode: code}
|
|
} |