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

@ -3,10 +3,12 @@ package capture
import (
"fmt"
"log"
"net"
"regexp"
"strings"
"sync"
"sync/atomic"
"github.com/google/gopacket"
"github.com/google/gopacket/pcap"
@ -38,6 +40,12 @@ type CaptureImpl struct {
isClosed bool
localIPs []string // Local IPs to filter (dst host)
linkType int // Link type from pcap handle
interfaceName string // Interface name (for diagnostics)
bpfFilter string // Applied BPF filter (for diagnostics)
// Metrics counters (atomic)
packetsReceived uint64 // Total packets received from interface
packetsSent uint64 // Total packets sent to channel
packetsDropped uint64 // Total packets dropped (channel full)
}
// New creates a new capture instance
@ -93,7 +101,6 @@ func (c *CaptureImpl) Run(cfg api.Config, out chan<- api.RawPacket) error {
c.mu.Lock()
c.handle = handle
c.linkType = int(handle.LinkType())
c.mu.Unlock()
defer func() {
@ -105,6 +112,9 @@ func (c *CaptureImpl) Run(cfg api.Config, out chan<- api.RawPacket) error {
c.mu.Unlock()
}()
// Store interface name for diagnostics
c.interfaceName = cfg.Interface
// Resolve local IPs for filtering (if not manually specified)
localIPs := cfg.LocalIPs
if len(localIPs) == 0 {
@ -113,7 +123,9 @@ func (c *CaptureImpl) Run(cfg api.Config, out chan<- api.RawPacket) error {
return fmt.Errorf("failed to detect local IPs: %w", err)
}
if len(localIPs) == 0 {
return fmt.Errorf("no local IPs found on interface %s", cfg.Interface)
// NAT/VIP: destination IP may not be assigned to this interface.
// Fall back to port-only BPF filter instead of aborting.
log.Printf("WARN capture: no local IPs found on interface %s; using port-only BPF filter (NAT/VIP mode)", cfg.Interface)
}
}
c.localIPs = localIPs
@ -123,6 +135,7 @@ func (c *CaptureImpl) Run(cfg api.Config, out chan<- api.RawPacket) error {
if bpfFilter == "" {
bpfFilter = c.buildBPFFilter(cfg.ListenPorts, localIPs)
}
c.bpfFilter = bpfFilter
// Validate BPF filter before applying
if err := validateBPFFilter(bpfFilter); err != nil {
@ -134,17 +147,27 @@ func (c *CaptureImpl) Run(cfg api.Config, out chan<- api.RawPacket) error {
return fmt.Errorf("failed to set BPF filter '%s': %w", bpfFilter, err)
}
// Store link type once, after the handle is fully configured (BPF filter applied).
// A single write avoids the race where packetToRawPacket reads a stale value
// that existed before the BPF filter was set.
c.mu.Lock()
c.linkType = int(handle.LinkType())
c.mu.Unlock()
packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
for packet := range packetSource.Packets() {
// Convert packet to RawPacket
rawPkt := c.packetToRawPacket(packet)
if rawPkt != nil {
atomic.AddUint64(&c.packetsReceived, 1)
select {
case out <- *rawPkt:
// Packet sent successfully
atomic.AddUint64(&c.packetsSent, 1)
default:
// Channel full, drop packet (could add metrics here)
// Channel full, drop packet
atomic.AddUint64(&c.packetsDropped, 1)
}
}
}
@ -196,7 +219,7 @@ func getInterfaceNames(ifaces []pcap.Interface) []string {
}
// detectLocalIPs detects local IP addresses on the specified interface
// Excludes loopback addresses (127.0.0.0/8, ::1)
// Excludes loopback addresses (127.0.0.0/8, ::1) and IPv6 link-local (fe80::)
func (c *CaptureImpl) detectLocalIPs(interfaceName string) ([]string, error) {
var localIPs []string
@ -220,7 +243,7 @@ func (c *CaptureImpl) detectLocalIPs(interfaceName string) ([]string, error) {
for _, addr := range addrs {
ip := extractIP(addr)
if ip != nil && !ip.IsLoopback() {
if ip != nil && !ip.IsLoopback() && !ip.IsLinkLocalUnicast() {
localIPs = append(localIPs, ip.String())
}
}
@ -242,7 +265,7 @@ func (c *CaptureImpl) detectLocalIPs(interfaceName string) ([]string, error) {
for _, addr := range addrs {
ip := extractIP(addr)
if ip != nil && !ip.IsLoopback() {
if ip != nil && !ip.IsLoopback() && !ip.IsLinkLocalUnicast() {
localIPs = append(localIPs, ip.String())
}
}
@ -358,3 +381,17 @@ func (c *CaptureImpl) Close() error {
c.isClosed = true
return nil
}
// GetStats returns capture statistics (for monitoring/debugging)
func (c *CaptureImpl) GetStats() (received, sent, dropped uint64) {
return atomic.LoadUint64(&c.packetsReceived),
atomic.LoadUint64(&c.packetsSent),
atomic.LoadUint64(&c.packetsDropped)
}
// GetDiagnostics returns capture diagnostics information (for debugging)
func (c *CaptureImpl) GetDiagnostics() (interfaceName string, localIPs []string, bpfFilter string, linkType int) {
c.mu.Lock()
defer c.mu.Unlock()
return c.interfaceName, c.localIPs, c.bpfFilter, c.linkType
}