Files
ja4sentinel/api/types.go
toto e166fdab2e
Some checks failed
Build RPM Package / Build RPM Packages (CentOS 7, Rocky 8/9/10) (push) Has been cancelled
feature: 1.1.18
+- FEATURE: Add comprehensive metrics for capture and TLS parser monitoring
+- Capture metrics: packets_received, packets_sent, packets_dropped (atomic counters)
+- Parser metrics: retransmit_count, gap_detected_count, buffer_exceeded_count, segment_exceeded_count
+- New GetStats() method on Capture interface for capture statistics
+- New GetMetrics() method on Parser interface for parser statistics
+- Add DefaultMaxHelloSegments constant (100) to prevent memory leaks from fragmented handshakes
+- Add Segments field to ConnectionFlow for per-flow segment tracking
+- Increase DefaultMaxTrackedFlows from 50000 to 100000 for high-traffic scenarios
+- Improve TCP reassembly: better handling of retransmissions and sequence gaps
+- Memory leak prevention: limit segments per flow and buffer size
+- Aggressive flow cleanup: clean up JA4_DONE flows when approaching flow limit
+- Lock ordering fix: release flow.mu before acquiring p.mu to avoid deadlocks
+- Exclude IPv6 link-local addresses (fe80::) from local IP detection
+- Improve error logging with detailed connection and TLS extension information
+- Add capture diagnostics logging (interface, link_type, local_ips, bpf_filter)
+- Fix false positive retransmission counter when SYN packet is missed
+- Fix gap handling: reset sequence tracking instead of dropping flow
+- Fix extractTLSExtensions: return error details with basic TLS info for debugging
2026-03-09 16:38:40 +01:00

304 lines
12 KiB
Go

package api
import (
"strings"
"time"
)
// ServiceLog represents internal service logging for diagnostics
type ServiceLog struct {
Level string `json:"level"`
Component string `json:"component"`
Message string `json:"message"`
Details map[string]string `json:"details,omitempty"`
Timestamp int64 `json:"timestamp,omitempty"` // Unix nanoseconds (auto-set by logger)
TraceID string `json:"trace_id,omitempty"` // Optional distributed tracing ID
ConnID string `json:"conn_id,omitempty"` // Optional TCP flow identifier
}
// Config holds basic network and TLS configuration
type Config struct {
Interface string `yaml:"interface" json:"interface"`
ListenPorts []uint16 `yaml:"listen_ports" json:"listen_ports"`
BPFFilter string `yaml:"bpf_filter" json:"bpf_filter,omitempty"`
LocalIPs []string `yaml:"local_ips" json:"local_ips,omitempty"` // Local IPs to monitor (empty = auto-detect, excludes loopback)
ExcludeSourceIPs []string `yaml:"exclude_source_ips" json:"exclude_source_ips,omitempty"` // Source IPs or CIDR ranges to exclude (e.g., ["10.0.0.0/8", "192.168.1.1"])
FlowTimeoutSec int `yaml:"flow_timeout_sec" json:"flow_timeout_sec,omitempty"` // Timeout for TLS handshake extraction (default: 30)
PacketBufferSize int `yaml:"packet_buffer_size" json:"packet_buffer_size,omitempty"` // Buffer size for packet channel (default: 1000)
LogLevel string `yaml:"log_level" json:"log_level,omitempty"` // Log level: debug, info, warn, error (default: info)
}
// IPMeta contains IP metadata for stack fingerprinting
type IPMeta struct {
TTL uint8 `json:"ttl"`
TotalLength uint16 `json:"total_length"`
IPID uint16 `json:"id"`
DF bool `json:"df"`
}
// TCPMeta contains TCP metadata for stack fingerprinting
type TCPMeta struct {
WindowSize uint16 `json:"window_size"`
MSS uint16 `json:"mss,omitempty"`
WindowScale uint8 `json:"window_scale,omitempty"`
Options []string `json:"options"`
}
// RawPacket represents a raw packet captured from the network
type RawPacket struct {
Data []byte `json:"-"` // Raw packet data including link-layer header
Timestamp int64 `json:"timestamp"` // nanoseconds since epoch
LinkType int `json:"-"` // Link type (1=Ethernet, 101=Linux SLL, etc.)
}
// TLSClientHello represents a client-side TLS ClientHello with IP/TCP metadata
type TLSClientHello struct {
SrcIP string `json:"src_ip"`
SrcPort uint16 `json:"src_port"`
DstIP string `json:"dst_ip"`
DstPort uint16 `json:"dst_port"`
Payload []byte `json:"-"` // Not serialized
IPMeta IPMeta `json:"ip_meta"`
TCPMeta TCPMeta `json:"tcp_meta"`
ConnID string `json:"conn_id,omitempty"` // Unique flow identifier
SNI string `json:"tls_sni,omitempty"` // Server Name Indication
ALPN string `json:"tls_alpn,omitempty"` // Application-Layer Protocol Negotiation
TLSVersion string `json:"tls_version,omitempty"` // Max TLS version supported
SynToCHMs *uint32 `json:"syn_to_clienthello_ms,omitempty"` // Time from SYN to ClientHello (ms)
}
// Fingerprints contains TLS fingerprints for a client flow
// Note: JA4Hash is kept for internal use but not serialized to LogRecord
// as the JA4 format already includes its own hash portions
type Fingerprints struct {
JA4 string `json:"ja4"`
JA4Hash string `json:"ja4_hash,omitempty"` // Internal use, not serialized to LogRecord
JA3 string `json:"ja3,omitempty"`
JA3Hash string `json:"ja3_hash,omitempty"`
}
// LogRecord is the final log record, serialized as a flat JSON object
type LogRecord struct {
SrcIP string `json:"src_ip"`
SrcPort uint16 `json:"src_port"`
DstIP string `json:"dst_ip"`
DstPort uint16 `json:"dst_port"`
// Flattened IPMeta fields
IPTTL uint8 `json:"ip_meta_ttl"`
IPTotalLen uint16 `json:"ip_meta_total_length"`
IPID uint16 `json:"ip_meta_id"`
IPDF bool `json:"ip_meta_df"`
// Flattened TCPMeta fields
TCPWindow uint16 `json:"tcp_meta_window_size"`
TCPMSS *uint16 `json:"tcp_meta_mss,omitempty"`
TCPWScale *uint8 `json:"tcp_meta_window_scale,omitempty"`
TCPOptions string `json:"tcp_meta_options"` // comma-separated list
// Correlation & Triage
ConnID string `json:"conn_id,omitempty"` // Unique flow identifier
SensorID string `json:"sensor_id,omitempty"` // Sensor/captor identifier
// TLS elements (ClientHello)
TLSVersion string `json:"tls_version,omitempty"` // Max TLS version announced by client
SNI string `json:"tls_sni,omitempty"` // Server Name Indication
ALPN string `json:"tls_alpn,omitempty"` // Application-Layer Protocol Negotiation
// Behavioral detection (Timing)
SynToCHMs *uint32 `json:"syn_to_clienthello_ms,omitempty"` // Time from SYN to ClientHello (ms)
// Fingerprints
// Note: ja4_hash is NOT included - the JA4 format already includes its own hash portions
JA4 string `json:"ja4"`
JA3 string `json:"ja3,omitempty"`
JA3Hash string `json:"ja3_hash,omitempty"`
// Timestamp in nanoseconds since Unix epoch
Timestamp int64 `json:"timestamp"`
}
// OutputConfig defines configuration for a single log output
type OutputConfig struct {
Type string `yaml:"type" json:"type"` // unix_socket, stdout, file, etc.
Enabled bool `yaml:"enabled" json:"enabled"` // whether this output is active
AsyncBuffer int `yaml:"async_buffer" json:"async_buffer"` // queue size for async writes (e.g., 5000)
Params map[string]string `yaml:"params" json:"params"` // specific parameters like socket_path, path, etc.
}
// AppConfig is the complete ja4sentinel configuration
type AppConfig struct {
Core Config `yaml:"core" json:"core"`
Outputs []OutputConfig `yaml:"outputs" json:"outputs"`
}
// Loader defines the interface for loading application configuration.
// Implementations must read configuration from a YAML file, merge with
// environment variables (JA4SENTINEL_*), and validate the final result.
type Loader interface {
Load() (AppConfig, error)
}
// Capture defines the interface for capturing raw network packets.
// Implementations must listen on a configured network interface, apply
// BPF filters for specified ports, and emit RawPacket objects to a channel.
// The Close method must be called to release resources (e.g., pcap handle).
type Capture interface {
Run(cfg Config, out chan<- RawPacket) error
Close() error
GetStats() (received, sent, dropped uint64)
}
// Parser defines the interface for extracting TLS ClientHello messages
// from raw network packets. Implementations must track TCP connection states,
// reassemble fragmented handshakes, and return TLSClientHello objects with
// IP/TCP metadata. Returns nil for non-TLS or non-ClientHello packets.
type Parser interface {
Process(pkt RawPacket) (*TLSClientHello, error)
Close() error
GetMetrics() (retransmit, gapDetected, bufferExceeded, segmentExceeded uint64)
}
// Engine defines the interface for generating TLS fingerprints.
// Implementations must analyze TLS ClientHello payloads and produce
// JA4 (required) and optionally JA3 fingerprint strings.
type Engine interface {
FromClientHello(ch TLSClientHello) (*Fingerprints, error)
}
// Writer defines the generic interface for writing log records.
// Implementations must serialize LogRecord objects and send them to
// a destination (stdout, file, UNIX socket, etc.).
type Writer interface {
Write(rec LogRecord) error
}
// UnixSocketWriter extends Writer with a Close method for UNIX socket cleanup.
// Implementations must connect to a UNIX socket at the specified path and
// write JSON-encoded LogRecord objects. Reconnection logic should be
// implemented for transient socket failures.
type UnixSocketWriter interface {
Writer
Close() error
}
// MultiWriter extends Writer to support multiple output destinations.
// Implementations must write each LogRecord to all registered writers
// and provide methods to add writers and close all connections.
type MultiWriter interface {
Writer
Add(writer Writer)
CloseAll() error
}
// Builder defines the interface for constructing output writers from configuration.
// Implementations must parse AppConfig.Outputs and create appropriate Writer
// instances (StdoutWriter, FileWriter, UnixSocketWriter), combining them
// into a MultiWriter if multiple outputs are configured.
type Builder interface {
NewFromConfig(cfg AppConfig) (Writer, error)
}
// Logger defines the interface for structured service logging.
// Implementations must emit JSON-formatted log entries to stdout/stderr
// with support for multiple log levels (DEBUG, INFO, WARN, ERROR).
// Each log entry includes timestamp, level, component, message, and optional details.
type Logger interface {
Debug(component, message string, details map[string]string)
Info(component, message string, details map[string]string)
Warn(component, message string, details map[string]string)
Error(component, message string, details map[string]string)
}
// Reopenable defines the interface for components that support log file rotation.
// Implementations must reopen their output files when receiving a SIGHUP signal.
// This is used by systemctl reload to switch to new log files after logrotate.
type Reopenable interface {
Reopen() error
}
// Helper functions for creating and converting records
// NewLogRecord creates a flattened LogRecord from TLSClientHello and Fingerprints.
// Converts TCPMeta options to a comma-separated string and creates pointer values
// for optional fields (MSS, WindowScale) to support proper JSON omitempty behavior.
// If fingerprints is nil, the JA4/JA3 fields will be empty strings.
// Note: JA4Hash is intentionally NOT included in LogRecord as the JA4 format
// already includes its own hash portions (the full 38-character JA4 string is sufficient).
func NewLogRecord(ch TLSClientHello, fp *Fingerprints) LogRecord {
opts := ""
if len(ch.TCPMeta.Options) > 0 {
opts = strings.Join(ch.TCPMeta.Options, ",")
}
// Helper to create pointer from value for optional fields
var mssPtr *uint16
if ch.TCPMeta.MSS != 0 {
mssPtr = &ch.TCPMeta.MSS
}
var wScalePtr *uint8
if ch.TCPMeta.WindowScale != 0 {
wScalePtr = &ch.TCPMeta.WindowScale
}
rec := LogRecord{
SrcIP: ch.SrcIP,
SrcPort: ch.SrcPort,
DstIP: ch.DstIP,
DstPort: ch.DstPort,
IPTTL: ch.IPMeta.TTL,
IPTotalLen: ch.IPMeta.TotalLength,
IPID: ch.IPMeta.IPID,
IPDF: ch.IPMeta.DF,
TCPWindow: ch.TCPMeta.WindowSize,
TCPMSS: mssPtr,
TCPWScale: wScalePtr,
TCPOptions: opts,
ConnID: ch.ConnID,
SNI: ch.SNI,
ALPN: ch.ALPN,
TLSVersion: ch.TLSVersion,
SynToCHMs: ch.SynToCHMs,
Timestamp: time.Now().UnixNano(),
}
if fp != nil {
rec.JA4 = fp.JA4
rec.JA3 = fp.JA3
rec.JA3Hash = fp.JA3Hash
}
return rec
}
// Default values and constants
const (
DefaultInterface = "eth0"
DefaultPort = 443
DefaultBPFFilter = ""
DefaultFlowTimeout = 30 // seconds
DefaultPacketBuffer = 1000 // packet channel buffer size
DefaultLogLevel = "info"
)
// DefaultConfig returns an AppConfig with sensible default values.
// Uses eth0 as the default interface, port 443 for monitoring,
// no BPF filter, a 30-second flow timeout, and a 1000-packet
// channel buffer. Returns an empty outputs slice (caller must
// configure outputs explicitly).
func DefaultConfig() AppConfig {
return AppConfig{
Core: Config{
Interface: DefaultInterface,
ListenPorts: []uint16{DefaultPort},
BPFFilter: DefaultBPFFilter,
FlowTimeoutSec: DefaultFlowTimeout,
PacketBufferSize: DefaultPacketBuffer,
LogLevel: DefaultLogLevel,
},
Outputs: []OutputConfig{},
}
}