Improves flow handling and adds runtime stats APIs
Refactors TCP and UDP flow managers to enhance analyzer selection and flow binding accuracy, including O(1) UDP stream rebinding by 5-tuple. Introduces runtime stats tracking for engine and ruleset operations, exposing new APIs for granular performance and error metrics. Optimizes GeoMatcher with result caching and supports efficient geosite set matching, reducing redundant computation in ruleset expressions.
This commit is contained in:
+68
-25
@@ -2,6 +2,7 @@ package engine
|
||||
|
||||
import (
|
||||
"context"
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
@@ -20,18 +21,34 @@ type engine struct {
|
||||
logger Logger
|
||||
io io.PacketIO
|
||||
workers []*worker
|
||||
verdicts sync.Map // streamID(uint32) → verdictEntry
|
||||
stats *statsCounters
|
||||
verdicts sync.Map // streamID(uint32) -> verdictEntry
|
||||
verdictsGen atomic.Int64 // incremented on ruleset update
|
||||
|
||||
overflowCh chan *workerPacket
|
||||
overflowOnce sync.Once
|
||||
overflowPolicy OverflowPolicy
|
||||
resultCh chan workerResult
|
||||
}
|
||||
|
||||
func NewEngine(config Config) (Engine, error) {
|
||||
workerCount := config.Workers
|
||||
if workerCount <= 0 {
|
||||
workerCount = 1
|
||||
workerCount = runtime.GOMAXPROCS(0)
|
||||
if workerCount <= 0 {
|
||||
workerCount = 1
|
||||
}
|
||||
}
|
||||
overflowPolicy := config.OverflowPolicy
|
||||
if overflowPolicy == "" {
|
||||
overflowPolicy = OverflowPolicyAccept
|
||||
}
|
||||
selectionMode := config.AnalyzerSelectionMode
|
||||
if selectionMode == "" {
|
||||
selectionMode = AnalyzerSelectionModeSignature
|
||||
}
|
||||
|
||||
stats := &statsCounters{}
|
||||
resultCh := make(chan workerResult, workerCount*256)
|
||||
|
||||
macResolver := newSourceMACResolver()
|
||||
var err error
|
||||
workers := make([]*worker, workerCount)
|
||||
@@ -45,16 +62,21 @@ func NewEngine(config Config) (Engine, error) {
|
||||
TCPMaxBufferedPagesTotal: config.WorkerTCPMaxBufferedPagesTotal,
|
||||
TCPMaxBufferedPagesPerConn: config.WorkerTCPMaxBufferedPagesPerConn,
|
||||
UDPMaxStreams: config.WorkerUDPMaxStreams,
|
||||
AnalyzerSelectionMode: selectionMode,
|
||||
ResultChan: resultCh,
|
||||
Stats: stats,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
e := &engine{
|
||||
logger: config.Logger,
|
||||
io: config.IO,
|
||||
workers: workers,
|
||||
overflowCh: make(chan *workerPacket, 1024),
|
||||
logger: config.Logger,
|
||||
io: config.IO,
|
||||
workers: workers,
|
||||
stats: stats,
|
||||
overflowPolicy: overflowPolicy,
|
||||
resultCh: resultCh,
|
||||
}
|
||||
return e, nil
|
||||
}
|
||||
@@ -74,13 +96,10 @@ func (e *engine) Run(ctx context.Context) error {
|
||||
ioCtx, ioCancel := context.WithCancel(ctx)
|
||||
defer ioCancel()
|
||||
|
||||
e.overflowOnce.Do(func() {
|
||||
go e.drainOverflow(ioCtx)
|
||||
})
|
||||
|
||||
for _, w := range e.workers {
|
||||
go w.Run(ioCtx)
|
||||
}
|
||||
go e.drainResults(ioCtx)
|
||||
|
||||
errChan := make(chan error, 1)
|
||||
err := e.io.Register(ioCtx, func(p io.Packet, err error) bool {
|
||||
@@ -121,24 +140,35 @@ func (e *engine) dispatch(p io.Packet) bool {
|
||||
gen := e.verdictsGen.Load()
|
||||
index := streamID % uint32(len(e.workers))
|
||||
wp := &workerPacket{
|
||||
StreamID: streamID,
|
||||
Data: data,
|
||||
SetVerdict: func(v io.Verdict, b []byte) error {
|
||||
if v == io.VerdictAcceptStream || v == io.VerdictDropStream {
|
||||
e.verdicts.Store(streamID, verdictEntry{Verdict: v, Gen: gen})
|
||||
}
|
||||
return e.io.SetVerdict(p, v, b)
|
||||
},
|
||||
Packet: p,
|
||||
StreamID: streamID,
|
||||
Data: data,
|
||||
Gen: gen,
|
||||
}
|
||||
if !e.workers[index].Feed(wp) {
|
||||
select {
|
||||
case e.overflowCh <- wp:
|
||||
e.stats.OverflowEvents.Add(1)
|
||||
switch e.overflowPolicy {
|
||||
case OverflowPolicyDrop:
|
||||
e.stats.OverflowDrops.Add(1)
|
||||
_ = e.io.SetVerdict(p, io.VerdictDrop, nil)
|
||||
case OverflowPolicyBackpressure:
|
||||
e.stats.OverflowBackpressureEvents.Add(1)
|
||||
e.workers[index].FeedBlocking(wp)
|
||||
default:
|
||||
e.stats.OverflowAccepts.Add(1)
|
||||
_ = e.io.SetVerdict(p, io.VerdictAccept, nil)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (e *engine) applyWorkerResult(r workerResult) {
|
||||
if r.Verdict == io.VerdictAcceptStream || r.Verdict == io.VerdictDropStream {
|
||||
e.verdicts.Store(r.StreamID, verdictEntry{Verdict: r.Verdict, Gen: r.Gen})
|
||||
}
|
||||
_ = e.io.SetVerdict(r.Packet, r.Verdict, r.ModifiedPacket)
|
||||
}
|
||||
|
||||
func validPacket(data []byte) bool {
|
||||
if len(data) == 0 {
|
||||
return false
|
||||
@@ -156,13 +186,26 @@ func validPacket(data []byte) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (e *engine) drainOverflow(ctx context.Context) {
|
||||
func (e *engine) drainResults(ctx context.Context) {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case wp := <-e.overflowCh:
|
||||
_ = wp.SetVerdict(io.VerdictAccept, nil)
|
||||
case r := <-e.resultCh:
|
||||
e.applyWorkerResult(r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (e *engine) Stats() Stats {
|
||||
return Stats{
|
||||
OverflowEvents: e.stats.OverflowEvents.Load(),
|
||||
OverflowAccepts: e.stats.OverflowAccepts.Load(),
|
||||
OverflowDrops: e.stats.OverflowDrops.Load(),
|
||||
OverflowBackpressureEvents: e.stats.OverflowBackpressureEvents.Load(),
|
||||
AnalyzerSelectionsTotal: e.stats.AnalyzerSelectionsTotal.Load(),
|
||||
AnalyzerSelectionsPruned: e.stats.AnalyzerSelectionsPruned.Load(),
|
||||
UDPTupleLookups: e.stats.UDPTupleLookups.Load(),
|
||||
UDPTupleHits: e.stats.UDPTupleHits.Load(),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user