feature: 1.1.18
Some checks failed
Build RPM Package / Build RPM Packages (CentOS 7, Rocky 8/9/10) (push) Has been cancelled

+- 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
This commit is contained in:
toto
2026-03-09 16:38:40 +01:00
parent d22b0634da
commit e166fdab2e
11 changed files with 448 additions and 100 deletions

View File

@ -445,45 +445,50 @@ func (w *UnixSocketWriter) writeWithReconnect(data []byte) error {
return nil
}
// Write writes a log record to the UNIX socket (non-blocking with queue)
// Write writes a log record to the UNIX socket (non-blocking with queue).
// Bug 12 fix: marshal JSON outside the lock, then hold mutex through both the
// isClosed check AND the non-blocking channel send so Close() cannot close the
// channel between those two operations.
func (w *UnixSocketWriter) Write(rec api.LogRecord) error {
w.mutex.Lock()
if w.isClosed {
w.mutex.Unlock()
return fmt.Errorf("writer is closed")
}
w.mutex.Unlock()
data, err := json.Marshal(rec)
if err != nil {
return fmt.Errorf("failed to marshal record: %w", err)
}
data = append(data, '\n')
w.mutex.Lock()
defer w.mutex.Unlock()
if w.isClosed {
return fmt.Errorf("writer is closed")
}
select {
case w.queue <- data:
return nil
default:
// Queue is full, drop the message (could also block or return error)
return fmt.Errorf("write queue is full, dropping message")
}
}
// Close closes the UNIX socket connection and stops the queue processor
// Close closes the UNIX socket connection and stops the queue processor.
// Bug 12 fix: set isClosed=true under mutex BEFORE closing the channel so a
// concurrent Write() sees the flag and returns early instead of panicking on
// a send-on-closed-channel.
func (w *UnixSocketWriter) Close() error {
w.closeOnce.Do(func() {
w.mutex.Lock()
w.isClosed = true
w.mutex.Unlock()
close(w.queueClose)
<-w.queueDone
close(w.queue)
w.mutex.Lock()
defer w.mutex.Unlock()
w.isClosed = true
if w.conn != nil {
w.conn.Close()
w.conn = nil
}
w.mutex.Unlock()
})
return nil
}

View File

@ -9,7 +9,9 @@ import (
"testing"
"time"
"ja4sentinel/api"
"sync"
"ja4sentinel/api"
)
func TestStdoutWriter(t *testing.T) {
@ -1057,3 +1059,40 @@ func TestUnixSocketWriter_ReconnectBackoff(t *testing.T) {
t.Error("Expected at least one error callback for nonexistent socket")
}
}
// TestUnixSocketWriter_WriteConcurrentClose_NoPanic verifies the Bug 12 fix:
// concurrent Write() and Close() must not panic with "send on closed channel".
// The test spins many goroutines calling Write() while a Close() races against them.
func TestUnixSocketWriter_WriteConcurrentClose_NoPanic(t *testing.T) {
const workers = 20
const iterations = 100
for trial := 0; trial < 5; trial++ {
w, err := NewUnixSocketWriterWithConfig(
"/tmp/bug12_test.sock",
time.Second, time.Second, 128,
)
if err != nil {
t.Fatalf("NewUnixSocketWriterWithConfig: %v", err)
}
rec := api.LogRecord{SrcIP: "1.2.3.4", JA4: "t13d_test"}
var wg sync.WaitGroup
wg.Add(workers)
for i := 0; i < workers; i++ {
go func() {
defer wg.Done()
for j := 0; j < iterations; j++ {
_ = w.Write(rec) // may return error but must not panic
}
}()
}
// Close races with the writes.
time.Sleep(time.Millisecond)
if err := w.Close(); err != nil {
t.Errorf("Close() error: %v", err)
}
wg.Wait()
}
}