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:
@ -90,6 +90,7 @@ type sessionRecord struct {
|
||||
H2WindowUpdate uint32 `json:"h2_window_update,omitempty"`
|
||||
H2PseudoOrder string `json:"h2_pseudo_order,omitempty"`
|
||||
H2HasPriority uint8 `json:"h2_has_priority,omitempty"`
|
||||
H2SettingsAck uint8 `json:"h2_settings_ack,omitempty"`
|
||||
H2HeaderTableSize *int32 `json:"h2_header_table_size,omitempty"`
|
||||
H2EnablePush *int32 `json:"h2_enable_push,omitempty"`
|
||||
H2MaxConcurrentStreams *int32 `json:"h2_max_concurrent_streams,omitempty"`
|
||||
@ -110,6 +111,11 @@ func NewClickHouseWriter(dsn string, batchSize int, flushInterval time.Duration)
|
||||
return nil, fmt.Errorf("analyse DSN ClickHouse: %w", err)
|
||||
}
|
||||
|
||||
// Désactiver l'insertion asynchrone pour les writes transactionnels
|
||||
opts.Settings = map[string]interface{}{
|
||||
"async_insert": 0,
|
||||
}
|
||||
|
||||
conn, err := clickhouse.Open(opts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("connexion ClickHouse: %w", err)
|
||||
@ -201,6 +207,10 @@ func (w *ClickHouseWriter) flushBatch(ctx context.Context, batch []*correlation.
|
||||
}
|
||||
|
||||
for _, s := range batch {
|
||||
// Ignorer les sessions sans aucune donnée applicative (SYN-only)
|
||||
if len(s.Requests) == 0 && s.TLS == nil {
|
||||
continue
|
||||
}
|
||||
record := sessionToRecord(s)
|
||||
jsonBytes, err := json.Marshal(record)
|
||||
if err != nil {
|
||||
@ -371,12 +381,29 @@ func sessionToRecord(s *correlation.SessionState) sessionRecord {
|
||||
}
|
||||
}
|
||||
|
||||
// Champs HTTP/2 au niveau connexion (H2ConnState)
|
||||
if s.H2Conn != nil {
|
||||
if s.H2Conn.SettingsAck {
|
||||
rec.H2SettingsAck = 1
|
||||
}
|
||||
// Vérifier si un stream a reçu une frame PRIORITY
|
||||
for _, stream := range s.H2Conn.Streams {
|
||||
if stream.Priority != nil {
|
||||
rec.H2HasPriority = 1
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rec
|
||||
}
|
||||
|
||||
// pseudoOrderToShort convertit la liste de pseudo-headers en notation abrégée.
|
||||
// Ex: [":method", ":authority", ":scheme", ":path"] → "m,a,s,p"
|
||||
func pseudoOrderToShort(headers []string) string {
|
||||
if len(headers) == 0 {
|
||||
return ""
|
||||
}
|
||||
short := make([]byte, 0, len(headers)*2-1)
|
||||
for i, h := range headers {
|
||||
if i > 0 {
|
||||
@ -391,6 +418,8 @@ func pseudoOrderToShort(headers []string) string {
|
||||
short = append(short, 's')
|
||||
case h == ":path":
|
||||
short = append(short, 'p')
|
||||
case h == ":status":
|
||||
short = append(short, 't')
|
||||
default:
|
||||
short = append(short, '?')
|
||||
}
|
||||
@ -506,7 +535,7 @@ func formatTCPOptions(opts []byte) string {
|
||||
kind := opts[i]
|
||||
switch kind {
|
||||
case 0: // End of Options List
|
||||
break
|
||||
return strings.Join(names, ",")
|
||||
case 1: // NOP
|
||||
names = append(names, "NOP")
|
||||
i++
|
||||
|
||||
Reference in New Issue
Block a user