fix: eliminate stale verdict poisoning, memory leaks, data races, and per-packet allocations in engine
This commit is contained in:
+40
-11
@@ -5,6 +5,7 @@ import (
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"git.difuse.io/Difuse/Mellaris/io"
|
||||
"git.difuse.io/Difuse/Mellaris/ruleset"
|
||||
@@ -13,10 +14,16 @@ import (
|
||||
var _ Engine = (*engine)(nil)
|
||||
|
||||
type verdictEntry struct {
|
||||
Verdict io.Verdict
|
||||
Gen int64
|
||||
Verdict io.Verdict
|
||||
Gen int64
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
const (
|
||||
verdictTTL = 15 * time.Second
|
||||
verdictSweepInterval = 15 * time.Second
|
||||
)
|
||||
|
||||
type engine struct {
|
||||
logger Logger
|
||||
io io.PacketIO
|
||||
@@ -39,7 +46,7 @@ func NewEngine(config Config) (Engine, error) {
|
||||
}
|
||||
overflowPolicy := config.OverflowPolicy
|
||||
if overflowPolicy == "" {
|
||||
overflowPolicy = OverflowPolicyAccept
|
||||
overflowPolicy = OverflowPolicyDrop
|
||||
}
|
||||
selectionMode := config.AnalyzerSelectionMode
|
||||
if selectionMode == "" {
|
||||
@@ -83,7 +90,6 @@ func NewEngine(config Config) (Engine, error) {
|
||||
|
||||
func (e *engine) UpdateRuleset(r ruleset.Ruleset) error {
|
||||
e.verdictsGen.Add(1)
|
||||
e.verdicts = sync.Map{}
|
||||
for _, w := range e.workers {
|
||||
if err := w.UpdateRuleset(r); err != nil {
|
||||
return err
|
||||
@@ -100,6 +106,7 @@ func (e *engine) Run(ctx context.Context) error {
|
||||
go w.Run(ioCtx)
|
||||
}
|
||||
go e.drainResults(ioCtx)
|
||||
go e.sweepVerdicts(ioCtx)
|
||||
|
||||
errChan := make(chan error, 1)
|
||||
err := e.io.Register(ioCtx, func(p io.Packet, err error) bool {
|
||||
@@ -124,11 +131,13 @@ func (e *engine) Run(ctx context.Context) error {
|
||||
func (e *engine) dispatch(p io.Packet) bool {
|
||||
streamID := p.StreamID()
|
||||
|
||||
if v, ok := e.verdicts.Load(streamID); ok {
|
||||
entry := v.(verdictEntry)
|
||||
if entry.Gen == e.verdictsGen.Load() {
|
||||
_ = e.io.SetVerdict(p, entry.Verdict, nil)
|
||||
return true
|
||||
if streamID != 0 {
|
||||
if v, ok := e.verdicts.Load(streamID); ok {
|
||||
entry := v.(verdictEntry)
|
||||
if entry.Gen == e.verdictsGen.Load() {
|
||||
_ = e.io.SetVerdict(p, entry.Verdict, nil)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,12 +172,32 @@ func (e *engine) dispatch(p io.Packet) bool {
|
||||
}
|
||||
|
||||
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})
|
||||
if r.StreamID != 0 && (r.Verdict == io.VerdictAcceptStream || r.Verdict == io.VerdictDropStream) {
|
||||
e.verdicts.Store(r.StreamID, verdictEntry{Verdict: r.Verdict, Gen: r.Gen, CreatedAt: time.Now()})
|
||||
}
|
||||
_ = e.io.SetVerdict(r.Packet, r.Verdict, r.ModifiedPacket)
|
||||
}
|
||||
|
||||
func (e *engine) sweepVerdicts(ctx context.Context) {
|
||||
ticker := time.NewTicker(verdictSweepInterval)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
now := time.Now()
|
||||
e.verdicts.Range(func(key, value interface{}) bool {
|
||||
entry := value.(verdictEntry)
|
||||
if now.Sub(entry.CreatedAt) > verdictTTL {
|
||||
e.verdicts.Delete(key)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func validPacket(data []byte) bool {
|
||||
if len(data) == 0 {
|
||||
return false
|
||||
|
||||
Reference in New Issue
Block a user