feat(ja4ebpf): add multi-interface TC, LPM_TRIE ignore_src, unit tests, and fix bugs
- Add multi-interface TC attachment (default "any" = all UP interfaces) - Add BPF LPM_TRIE map ignored_src for kernel-side CIDR filtering - Add userspace ignore_src filtering for SSL/accept4 path via net.IPNet.Contains() - Add AcceptCache for fd→SessionKey correlation with TTL and Close() - Add 5 test files covering writer, procutil, dispatcher, accept_cache, and cmd - Fix formatTCPOptions infinite loop on EOL (case 0 break→return) - Fix pseudoOrderToShort panic on empty slice (negative cap) - Fix AcceptCache goroutine leak (add done channel + Close()) - Update config.yml.example with interfaces, listen_ports, ignore_src - Rewrite docs/services/ja4ebpf.md (was massively stale: XDP, RingBuffer, etc.) - Fix stale XDP/RingBuffer references in docs/architecture.md, thesis, tls.go Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@ -1,401 +1,16 @@
|
||||
// Package parser fournit les parseurs pour les protocoles HTTP/1.x, HTTP/2 et TLS.
|
||||
//
|
||||
// Le parsing HTTP/2 est désormais assuré par internal/parser/h2conn.go qui utilise
|
||||
// golang.org/x/net/http2.Framer et golang.org/x/net/http2/hpack.Decoder pour une
|
||||
// conformité RFC complète, incluant la table dynamique HPACK et l'assemblage
|
||||
// HEADERS+CONTINUATION.
|
||||
//
|
||||
// Ce fichier ne conserve que les constantes et le filtre d'en-têtes partagés.
|
||||
package parser
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// H2Magic est la préface HTTP/2 client (RFC 7540 §3.5), exportée pour usage
|
||||
// par le routeur Magic Bytes (package dispatcher) et les consommateurs RingBuffer.
|
||||
const H2Magic = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"
|
||||
|
||||
// h2MagicPrefaceLen est la longueur du préambule HTTP/2 client.
|
||||
const h2MagicPrefaceLen = 24
|
||||
|
||||
// h2MagicPreface est le préambule ("PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n") envoyé
|
||||
// par tout client HTTP/2 avant la première frame SETTINGS.
|
||||
var h2MagicPreface = []byte(H2Magic)
|
||||
|
||||
// Identifiants de types de frames HTTP/2 (RFC 7540, §11.2).
|
||||
const (
|
||||
h2FrameData = 0
|
||||
h2FrameHeaders = 1
|
||||
h2FramePriority = 2
|
||||
h2FrameRSTStream = 3
|
||||
h2FrameSettings = 4
|
||||
h2FramePushPromise = 5
|
||||
h2FramePing = 6
|
||||
h2FrameGoAway = 7
|
||||
h2FrameWindowUpdate = 8
|
||||
h2FrameContinuation = 9
|
||||
)
|
||||
|
||||
// Identifiants des paramètres SETTINGS (RFC 7540, §11.3 + RFC 8441).
|
||||
const (
|
||||
h2SettingHeaderTableSize = 1
|
||||
h2SettingEnablePush = 2
|
||||
h2SettingMaxConcurrentStreams = 3
|
||||
h2SettingInitialWindowSize = 4
|
||||
h2SettingMaxFrameSize = 5
|
||||
h2SettingMaxHeaderListSize = 6
|
||||
h2SettingEnableConnectProtocol = 8
|
||||
)
|
||||
|
||||
// h2FrameHeader représente l'en-tête fixe de 9 octets d'une frame HTTP/2.
|
||||
type h2FrameHeader struct {
|
||||
Length uint32 // longueur du payload (3 octets)
|
||||
Type uint8 // type de frame
|
||||
Flags uint8 // flags
|
||||
StreamID uint32 // identifiant de stream (masque 0x7FFFFFFF)
|
||||
}
|
||||
|
||||
// parseH2FrameHeader décode l'en-tête de 9 octets d'une frame HTTP/2.
|
||||
func parseH2FrameHeader(data []byte) (h2FrameHeader, error) {
|
||||
if len(data) < 9 {
|
||||
return h2FrameHeader{}, fmt.Errorf("données insuffisantes pour l'en-tête frame HTTP/2: %d octets", len(data))
|
||||
}
|
||||
// Longueur sur 3 octets big-endian
|
||||
length := uint32(data[0])<<16 | uint32(data[1])<<8 | uint32(data[2])
|
||||
return h2FrameHeader{
|
||||
Length: length,
|
||||
Type: data[3],
|
||||
Flags: data[4],
|
||||
StreamID: binary.BigEndian.Uint32(data[5:9]) & 0x7FFFFFFF,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DetectH2Preface vérifie si le buffer commence par le préambule HTTP/2.
|
||||
func DetectH2Preface(data []byte) bool {
|
||||
if len(data) < h2MagicPrefaceLen {
|
||||
return false
|
||||
}
|
||||
for i := 0; i < h2MagicPrefaceLen; i++ {
|
||||
if data[i] != h2MagicPreface[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// H2MagicPrefaceLen retourne la longueur du préambule HTTP/2.
|
||||
func H2MagicPrefaceLen() int {
|
||||
return h2MagicPrefaceLen
|
||||
}
|
||||
|
||||
// HTTP2Settings contient les paramètres SETTINGS et WINDOW_UPDATE du client HTTP/2.
|
||||
type HTTP2Settings struct {
|
||||
HeaderTableSize int32 // SETTINGS_HEADER_TABLE_SIZE (-1 si absent)
|
||||
EnablePush int32 // SETTINGS_ENABLE_PUSH
|
||||
MaxConcurrentStreams int32 // SETTINGS_MAX_CONCURRENT_STREAMS
|
||||
InitialWindowSize int32 // SETTINGS_INITIAL_WINDOW_SIZE
|
||||
MaxFrameSize int32 // SETTINGS_MAX_FRAME_SIZE
|
||||
MaxHeaderListSize int32 // SETTINGS_MAX_HEADER_LIST_SIZE
|
||||
UnknownSettings int32 // paramètre 0x7 (JA4H2)
|
||||
EnableConnectProtocol int32 // SETTINGS_ENABLE_CONNECT_PROTOCOL (0x8, RFC 8441)
|
||||
WindowUpdateIncrement uint32 // valeur WINDOW_UPDATE sur stream 0
|
||||
PseudoHeaderOrder []string // ordre des pseudo-headers [:method, :authority, ...]
|
||||
HeaderKV map[string]string // en-têtes extraits du premier HEADERS frame
|
||||
HeaderOrder []string // noms des en-têtes dans l'ordre d'arrivée
|
||||
}
|
||||
|
||||
// ParseH2ClientPreface extrait les paramètres SETTINGS et le WINDOW_UPDATE
|
||||
// depuis le flux HTTP/2 déchiffré du client.
|
||||
// data doit commencer APRÈS le magic preface (offset 24).
|
||||
func ParseH2ClientPreface(data []byte) (*HTTP2Settings, error) {
|
||||
settings := &HTTP2Settings{
|
||||
HeaderTableSize: -1,
|
||||
EnablePush: -1,
|
||||
MaxConcurrentStreams: -1,
|
||||
InitialWindowSize: -1,
|
||||
MaxFrameSize: -1,
|
||||
MaxHeaderListSize: -1,
|
||||
UnknownSettings: -1,
|
||||
EnableConnectProtocol: -1,
|
||||
}
|
||||
|
||||
offset := 0
|
||||
// Parser au maximum 10 frames pour éviter une boucle infinie
|
||||
for frameIdx := 0; frameIdx < 10 && offset < len(data); frameIdx++ {
|
||||
if offset+9 > len(data) {
|
||||
break
|
||||
}
|
||||
|
||||
hdr, err := parseH2FrameHeader(data[offset:])
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
offset += 9
|
||||
|
||||
payloadEnd := offset + int(hdr.Length)
|
||||
if payloadEnd > len(data) {
|
||||
break
|
||||
}
|
||||
payload := data[offset:payloadEnd]
|
||||
offset = payloadEnd
|
||||
|
||||
switch hdr.Type {
|
||||
case h2FrameSettings:
|
||||
// Parser uniquement les SETTINGS du client (stream 0)
|
||||
if hdr.StreamID == 0 {
|
||||
pairs, err := parseH2SettingsFrame(payload)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for id, val := range pairs {
|
||||
switch id {
|
||||
case h2SettingHeaderTableSize:
|
||||
settings.HeaderTableSize = int32(val)
|
||||
case h2SettingEnablePush:
|
||||
settings.EnablePush = int32(val)
|
||||
case h2SettingMaxConcurrentStreams:
|
||||
settings.MaxConcurrentStreams = int32(val)
|
||||
case h2SettingInitialWindowSize:
|
||||
settings.InitialWindowSize = int32(val)
|
||||
case h2SettingMaxFrameSize:
|
||||
settings.MaxFrameSize = int32(val)
|
||||
case h2SettingMaxHeaderListSize:
|
||||
settings.MaxHeaderListSize = int32(val)
|
||||
case 7: // paramètre non standard (JA4H2)
|
||||
settings.UnknownSettings = int32(val)
|
||||
case h2SettingEnableConnectProtocol:
|
||||
settings.EnableConnectProtocol = int32(val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case h2FrameWindowUpdate:
|
||||
// WINDOW_UPDATE sur stream 0 = flux de connexion
|
||||
if hdr.StreamID == 0 && len(payload) >= 4 {
|
||||
settings.WindowUpdateIncrement = binary.BigEndian.Uint32(payload[0:4]) & 0x7FFFFFFF
|
||||
}
|
||||
|
||||
case h2FrameHeaders:
|
||||
// Extraire l'ordre des pseudo-headers et les en-têtes réguliers
|
||||
if hdr.StreamID > 0 && len(settings.PseudoHeaderOrder) == 0 {
|
||||
settings.PseudoHeaderOrder = ParseH2PseudoHeaders(payload)
|
||||
// Extraire aussi les en-têtes réguliers (User-Agent, Accept, etc.)
|
||||
kv, order := DecodeH2HeadersBlock(payload)
|
||||
if len(kv) > 0 {
|
||||
settings.HeaderKV = kv
|
||||
settings.HeaderOrder = order
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return settings, nil
|
||||
}
|
||||
|
||||
// parseH2SettingsFrame extrait les paires (identifiant, valeur) d'une frame SETTINGS.
|
||||
// Chaque paire fait 6 octets : identifiant(2) + valeur(4).
|
||||
func parseH2SettingsFrame(payload []byte) (map[uint16]uint32, error) {
|
||||
if len(payload)%6 != 0 {
|
||||
return nil, fmt.Errorf("longueur de frame SETTINGS invalide: %d (doit être multiple de 6)", len(payload))
|
||||
}
|
||||
result := make(map[uint16]uint32)
|
||||
for i := 0; i+6 <= len(payload); i += 6 {
|
||||
id := binary.BigEndian.Uint16(payload[i : i+2])
|
||||
val := binary.BigEndian.Uint32(payload[i+2 : i+6])
|
||||
result[id] = val
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// ParseH2PseudoHeaders extrait l'ordre des pseudo-headers depuis un bloc HPACK.
|
||||
//
|
||||
// Implémentation simplifiée : détecte les pseudo-headers via les index HPACK statiques.
|
||||
// Table statique HPACK (RFC 7541, Annexe A) — index pertinents :
|
||||
// 1 :authority
|
||||
// 2 :method = GET
|
||||
// 3 :method = POST
|
||||
// 4 :path = /
|
||||
// 5 :path = /index.html
|
||||
// 6 :scheme = http
|
||||
// 7 :scheme = https
|
||||
func ParseH2PseudoHeaders(headersBlock []byte) []string {
|
||||
// Index HPACK statique → pseudo-header
|
||||
hpackStaticPseudo := map[int]string{
|
||||
1: ":authority",
|
||||
2: ":method",
|
||||
3: ":method",
|
||||
4: ":path",
|
||||
5: ":path",
|
||||
6: ":scheme",
|
||||
7: ":scheme",
|
||||
}
|
||||
|
||||
seen := make(map[string]bool)
|
||||
var order []string
|
||||
offset := 0
|
||||
|
||||
for offset < len(headersBlock) {
|
||||
b := headersBlock[offset]
|
||||
|
||||
// Représentation indexée (bit 7 = 1) : RFC 7541 §6.1
|
||||
if b&0x80 != 0 {
|
||||
idx := int(b & 0x7F)
|
||||
if name, ok := hpackStaticPseudo[idx]; ok {
|
||||
if !seen[name] {
|
||||
seen[name] = true
|
||||
order = append(order, name)
|
||||
}
|
||||
} else if idx == 0 {
|
||||
// Fin de la liste d'index ou encodage multi-octets
|
||||
offset++
|
||||
continue
|
||||
} else {
|
||||
// Index dynamique ou non-pseudo-header : arrêter le scan
|
||||
break
|
||||
}
|
||||
offset++
|
||||
continue
|
||||
}
|
||||
|
||||
// Représentation littérale avec index incrémental (bits 7-6 = 01) : RFC 7541 §6.2.1
|
||||
if b&0xC0 == 0x40 {
|
||||
idx := int(b & 0x3F)
|
||||
if name, ok := hpackStaticPseudo[idx]; ok {
|
||||
if !seen[name] {
|
||||
seen[name] = true
|
||||
order = append(order, name)
|
||||
}
|
||||
}
|
||||
offset++
|
||||
// Sauter la valeur (longueur + contenu)
|
||||
if offset >= len(headersBlock) {
|
||||
break
|
||||
}
|
||||
valueLen := int(headersBlock[offset] & 0x7F) // ignorer le bit Huffman
|
||||
offset += 1 + valueLen
|
||||
continue
|
||||
}
|
||||
|
||||
// Tout autre encodage : arrêter (ce n'est probablement plus un pseudo-header)
|
||||
break
|
||||
}
|
||||
|
||||
return order
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// HPACK static table (RFC 7541, Appendix A) — index → header name
|
||||
// Seuls les noms sont listés (les valeurs par défaut sont ignorées car
|
||||
// les en-têtes d'intérêt comme User-Agent sont toujours envoyés en littéral).
|
||||
// ---------------------------------------------------------------------------
|
||||
// hpackStaticEntry est une entrée de la table statique HPACK (RFC 7541 Appendix A).
|
||||
type hpackStaticEntry struct {
|
||||
Name string
|
||||
Value string
|
||||
}
|
||||
|
||||
// hpackStaticTable est la table statique HPACK (RFC 7541 Appendix A).
|
||||
// Index 1-61 : RFC 7541 original. Index 62-100 : extensions RFC 9204 + navigateurs.
|
||||
var hpackStaticTable = map[int]hpackStaticEntry{
|
||||
1: {":authority", ""},
|
||||
2: {":method", "GET"},
|
||||
3: {":method", "POST"},
|
||||
4: {":path", "/"},
|
||||
5: {":path", "/index.html"},
|
||||
6: {":scheme", "http"},
|
||||
7: {":scheme", "https"},
|
||||
8: {":status", "200"},
|
||||
9: {":status", "204"},
|
||||
10: {":status", "206"},
|
||||
11: {":status", "304"},
|
||||
12: {":status", "400"},
|
||||
13: {":status", "404"},
|
||||
14: {":status", "500"},
|
||||
15: {"accept-charset", ""},
|
||||
16: {"accept-encoding", "gzip, deflate"},
|
||||
17: {"accept-language", ""},
|
||||
18: {"accept", ""},
|
||||
19: {"accept", "*/*"},
|
||||
20: {"access-control-allow-origin", ""},
|
||||
21: {"accept-encoding", ""},
|
||||
22: {"accept-encoding", "gzip, deflate"},
|
||||
23: {"accept-language", ""},
|
||||
24: {"accept-language", ""},
|
||||
25: {"access-control-allow-credentials", ""},
|
||||
26: {"access-control-allow-headers", ""},
|
||||
27: {"access-control-allow-methods", ""},
|
||||
28: {"access-control-allow-origin", ""},
|
||||
29: {"access-control-request-headers", ""},
|
||||
30: {"access-control-request-method", ""},
|
||||
31: {"age", ""},
|
||||
32: {"authorization", ""},
|
||||
33: {"cache-control", ""},
|
||||
34: {"cache-control", "max-age=0"},
|
||||
35: {"cookie", ""},
|
||||
36: {"cookie", ""},
|
||||
37: {"date", ""},
|
||||
38: {"etag", ""},
|
||||
39: {"expect", ""},
|
||||
40: {"from", ""},
|
||||
41: {"host", ""},
|
||||
42: {"if-match", ""},
|
||||
43: {"if-modified-since", ""},
|
||||
44: {"if-none-match", ""},
|
||||
45: {"if-range", ""},
|
||||
46: {"if-unmodified-since", ""},
|
||||
47: {"last-modified", ""},
|
||||
48: {"link", ""},
|
||||
49: {"location", ""},
|
||||
50: {"max-forwards", ""},
|
||||
51: {"proxy-authenticate", ""},
|
||||
52: {"proxy-authorization", ""},
|
||||
53: {"range", ""},
|
||||
54: {"referer", ""},
|
||||
55: {"refresh", ""},
|
||||
56: {"retry-after", ""},
|
||||
57: {"server", ""},
|
||||
58: {"set-cookie", ""},
|
||||
59: {"strict-transport-security", ""},
|
||||
60: {"transfer-encoding", ""},
|
||||
61: {"user-agent", ""},
|
||||
62: {"vary", ""},
|
||||
63: {"vary", "Accept-Encoding"},
|
||||
64: {"via", ""},
|
||||
65: {"www-authenticate", ""},
|
||||
66: {"x-forwarded-for", ""},
|
||||
67: {"x-forwarded-proto", ""},
|
||||
68: {"x-requested-with", ""},
|
||||
69: {"sec-websocket-key", ""},
|
||||
70: {"sec-websocket-version", ""},
|
||||
71: {"te", ""},
|
||||
72: {"upgrade", ""},
|
||||
73: {"sec-ch-ua", ""},
|
||||
74: {"sec-ch-ua-mobile", "?0"},
|
||||
75: {"sec-ch-ua-platform", ""},
|
||||
76: {"sec-fetch-dest", ""},
|
||||
77: {"sec-fetch-mode", ""},
|
||||
78: {"sec-fetch-site", ""},
|
||||
79: {"sec-fetch-user", "?1"},
|
||||
80: {"priority", ""},
|
||||
81: {"accept", ""},
|
||||
82: {"accept", "application/dns-message"},
|
||||
83: {"accept-language", ""},
|
||||
84: {":method", "CONNECT"},
|
||||
85: {":method", "DELETE"},
|
||||
86: {":method", "HEAD"},
|
||||
87: {":method", "OPTIONS"},
|
||||
88: {":method", "PATCH"},
|
||||
89: {":method", "PUT"},
|
||||
90: {":method", "TRACE"},
|
||||
91: {":path", "/"},
|
||||
92: {":path", "/0"},
|
||||
93: {":path", "/1"},
|
||||
94: {":path", "/2"},
|
||||
95: {":path", "/3"},
|
||||
96: {":path", "/4"},
|
||||
97: {":path", "/5"},
|
||||
98: {":path", "/6"},
|
||||
99: {":path", "/7"},
|
||||
100: {":path", "/8"},
|
||||
}
|
||||
|
||||
// hpackCapturedHeaders est la liste des en-têtes H2 dont on capture la valeur.
|
||||
var hpackCapturedHeaders = map[string]bool{
|
||||
// hpackCapturedHeaders est la liste des en-têtes HTTP/2 dont on capture la valeur.
|
||||
// Utilisé par h2conn.go pour filtrer les en-têtes décodés.
|
||||
var HpackCapturedHeaders = map[string]bool{
|
||||
"user-agent": true,
|
||||
"accept": true,
|
||||
"accept-encoding": true,
|
||||
@ -414,229 +29,8 @@ var hpackCapturedHeaders = map[string]bool{
|
||||
":path": true,
|
||||
":authority": true,
|
||||
":scheme": true,
|
||||
":status": true,
|
||||
"cookie": true,
|
||||
"referer": true,
|
||||
"host": true,
|
||||
}
|
||||
|
||||
// hpackInteger décode un entier HPACK avec le préfixe spécifié (RFC 7541 §5.1).
|
||||
// Retourne la valeur décodée et le nombre d'octets consommés.
|
||||
func hpackInteger(data []byte, prefixBits int) (int, int) {
|
||||
if len(data) == 0 {
|
||||
return 0, 0
|
||||
}
|
||||
mask := (1 << prefixBits) - 1
|
||||
value := int(data[0] & byte(mask))
|
||||
offset := 1
|
||||
|
||||
if value < mask {
|
||||
return value, offset
|
||||
}
|
||||
|
||||
// Extension multi-octets
|
||||
m := 0
|
||||
for offset < len(data) && offset < 6 { // limite de sécurité
|
||||
b := int(data[offset])
|
||||
value += (b & 0x7F) << m
|
||||
m += 7
|
||||
offset++
|
||||
if b&0x80 == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return value, offset
|
||||
}
|
||||
|
||||
// hpackString décode une chaîne HPACK (RFC 7541 §5.2).
|
||||
// Retourne la chaîne décodée et le nombre d'octets consommés.
|
||||
// Le décodage Huffman n'est pas implémenté — les chaînes Huffman sont ignorées.
|
||||
func hpackString(data []byte) (string, int) {
|
||||
if len(data) == 0 {
|
||||
return "", 0
|
||||
}
|
||||
isHuffman := data[0]&0x80 != 0
|
||||
length, offset := hpackInteger(data, 7)
|
||||
|
||||
if isHuffman {
|
||||
// Huffman non implémenté — on ne peut pas décoder la valeur
|
||||
return "", offset + length
|
||||
}
|
||||
|
||||
if offset+length > len(data) {
|
||||
// Données tronquées — retourner ce qu'on peut
|
||||
if offset < len(data) {
|
||||
return string(data[offset:]), len(data)
|
||||
}
|
||||
return "", offset
|
||||
}
|
||||
|
||||
return string(data[offset : offset+length]), offset + length
|
||||
}
|
||||
|
||||
// DecodeH2HeadersBlock décode un bloc d'en-têtes HPACK depuis un HEADERS frame.
|
||||
// Retourne un map nom→valeur et la liste ordonnée des noms.
|
||||
// Gère les représentations les plus courantes :
|
||||
// - Indexée (6.1) : index → nom+valeur de la table statique
|
||||
// - Littérale avec index incrémental (6.2.1) : nom indexé + valeur littérale
|
||||
// - Littérale sans indexation (6.2.2) : nom indexé + valeur littérale
|
||||
// - Littérale jamais indexée (6.2.3) : nom indexé + valeur littérale
|
||||
// - Nouveau nom littéral (6.2.x avec index=0) : nom littéral + valeur littérale
|
||||
func DecodeH2HeadersBlock(block []byte) (map[string]string, []string) {
|
||||
kv := make(map[string]string)
|
||||
var order []string
|
||||
offset := 0
|
||||
|
||||
for offset < len(block) && len(kv) < 50 { // limite de sécurité
|
||||
b := block[offset]
|
||||
|
||||
// 1. Représentation indexée (bit 7 = 1) : RFC 7541 §6.1
|
||||
if b&0x80 != 0 {
|
||||
idx, n := hpackInteger(block[offset:], 7)
|
||||
offset += n
|
||||
if idx > 0 && idx <= len(hpackStaticTable) {
|
||||
// Uniquement indexée — nom et valeur viennent de la table
|
||||
// Pour les entrées "nom uniquement" (pas de valeur par défaut),
|
||||
// on ne peut pas extraire la valeur sans table dynamique
|
||||
_ = hpackStaticTable[idx] // will be replaced
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
var name string
|
||||
var nameLen int
|
||||
|
||||
// 2. Littérale avec index incrémental (bits 7-6 = 01) : RFC 7541 §6.2.1
|
||||
if b&0xC0 == 0x40 {
|
||||
idx, n := hpackInteger(block[offset:], 6)
|
||||
offset += n
|
||||
|
||||
if idx == 0 {
|
||||
// Nouveau nom : nom littéral suivi de valeur littérale
|
||||
name, nameLen = hpackString(block[offset:])
|
||||
offset += nameLen
|
||||
} else if idx <= len(hpackStaticTable) {
|
||||
name = hpackStaticTable[idx].Name
|
||||
}
|
||||
|
||||
value, valueLen := hpackString(block[offset:])
|
||||
offset += valueLen
|
||||
|
||||
nameLower := strings.ToLower(name)
|
||||
if nameLower != "" && value != "" && hpackCapturedHeaders[nameLower] {
|
||||
kv[nameLower] = value
|
||||
order = append(order, nameLower)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// 3. Littérale sans indexation (bits 7-5 = 000) : RFC 7541 §6.2.2
|
||||
if b&0xF0 == 0x00 {
|
||||
idx, n := hpackInteger(block[offset:], 4)
|
||||
offset += n
|
||||
|
||||
if idx == 0 {
|
||||
name, nameLen = hpackString(block[offset:])
|
||||
offset += nameLen
|
||||
} else if idx <= len(hpackStaticTable) {
|
||||
name = hpackStaticTable[idx].Name
|
||||
}
|
||||
|
||||
value, valueLen := hpackString(block[offset:])
|
||||
offset += valueLen
|
||||
|
||||
nameLower := strings.ToLower(name)
|
||||
if nameLower != "" && value != "" && hpackCapturedHeaders[nameLower] {
|
||||
kv[nameLower] = value
|
||||
order = append(order, nameLower)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// 4. Littérale jamais indexée (bits 7-5 = 0001) : RFC 7541 §6.2.3
|
||||
if b&0xF0 == 0x10 {
|
||||
idx, n := hpackInteger(block[offset:], 4)
|
||||
offset += n
|
||||
|
||||
if idx == 0 {
|
||||
name, nameLen = hpackString(block[offset:])
|
||||
offset += nameLen
|
||||
} else if idx <= len(hpackStaticTable) {
|
||||
name = hpackStaticTable[idx].Name
|
||||
}
|
||||
|
||||
value, valueLen := hpackString(block[offset:])
|
||||
offset += valueLen
|
||||
|
||||
nameLower := strings.ToLower(name)
|
||||
if nameLower != "" && value != "" && hpackCapturedHeaders[nameLower] {
|
||||
kv[nameLower] = value
|
||||
order = append(order, nameLower)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Représentation inconnue — arrêter
|
||||
break
|
||||
}
|
||||
|
||||
return kv, order
|
||||
}
|
||||
|
||||
// IsH2FrameHeader vérifie si les données commencent par un en-tête de frame HTTP/2 valide.
|
||||
// Utilisé pour détecter les frames H2 seules (sans préface) dans les SSL_read ultérieurs.
|
||||
func IsH2FrameHeader(data []byte) bool {
|
||||
if len(data) < 9 {
|
||||
return false
|
||||
}
|
||||
hdr, err := parseH2FrameHeader(data)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
// Vérifications de plausibilité :
|
||||
// - Longueur ≤ 16384 (16 KiB, limite conservatrice pour un seul read)
|
||||
// - Type dans la plage 0-9 (types de frame définis)
|
||||
// - Stream ID dans une plage raisonnable
|
||||
if hdr.Length > 16384 {
|
||||
return false
|
||||
}
|
||||
if hdr.Type > 9 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// ExtractH2HeaderKV extrait les en-têtes des frames HEADERS HTTP/2.
|
||||
// Parcourt toutes les frames dans les données et décode les blocs HEADERS.
|
||||
func ExtractH2HeaderKV(data []byte) map[string]string {
|
||||
kv := make(map[string]string)
|
||||
offset := 0
|
||||
|
||||
for offset < len(data) && len(kv) < 50 {
|
||||
if offset+9 > len(data) {
|
||||
break
|
||||
}
|
||||
hdr, err := parseH2FrameHeader(data[offset:])
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
offset += 9
|
||||
|
||||
payloadEnd := offset + int(hdr.Length)
|
||||
if payloadEnd > len(data) {
|
||||
break
|
||||
}
|
||||
payload := data[offset:payloadEnd]
|
||||
offset = payloadEnd
|
||||
|
||||
if hdr.Type == h2FrameHeaders && hdr.StreamID > 0 {
|
||||
frameKV, _ := DecodeH2HeadersBlock(payload)
|
||||
for k, v := range frameKV {
|
||||
if _, exists := kv[k]; !exists {
|
||||
kv[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return kv
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user