@@ -7,14 +7,13 @@ package waf
77
88import (
99 "sync"
10+ "sync/atomic"
1011 "time"
1112
1213 "github.com/DataDog/go-libddwaf/v3/errors"
1314 "github.com/DataDog/go-libddwaf/v3/internal/bindings"
1415 "github.com/DataDog/go-libddwaf/v3/internal/unsafe"
1516 "github.com/DataDog/go-libddwaf/v3/timer"
16-
17- "sync/atomic"
1817)
1918
2019// Context is a WAF execution context. It allows running the WAF incrementally
@@ -26,9 +25,10 @@ type Context struct {
2625 cgoRefs cgoRefPool // Used to retain go data referenced by WAF Objects the context holds
2726 cContext bindings.WafContext // The C ddwaf_context pointer
2827
29- timeoutCount atomic.Uint64 // Cumulative timeout count for this context.
28+ // timeoutCount count all calls which have timeout'ed by scope. Keys are fixed at creation time.
29+ timeoutCount map [Scope ]* atomic.Uint64
3030
31- // Mutex protecting the use of cContext which is not thread-safe and cgoRefs.
31+ // mutex protecting the use of cContext which is not thread-safe and cgoRefs.
3232 mutex sync.Mutex
3333
3434 // timer registers the time spent in the WAF and go-libddwaf
@@ -39,7 +39,7 @@ type Context struct {
3939
4040 // truncations provides details about truncations that occurred while
4141 // encoding address data for WAF execution.
42- truncations map [TruncationReason ][]int
42+ truncations map [Scope ] map [ TruncationReason ][]int
4343}
4444
4545// RunAddressData provides address data to the Context.Run method. If a given key is present in both
@@ -51,6 +51,8 @@ type RunAddressData struct {
5151 // Ephemeral address data is scoped to a given Context.Run call and is not persisted across calls. This is used for
5252 // protocols such as gRPC client/server streaming or GraphQL, where a single request can incur multiple subrequests.
5353 Ephemeral map [string ]any
54+ // Scope is the way to classify the different runs in the same context in order to have different metrics
55+ Scope Scope
5456}
5557
5658func (d RunAddressData ) isEmpty () bool {
@@ -70,9 +72,13 @@ func (context *Context) Run(addressData RunAddressData) (res Result, err error)
7072 return
7173 }
7274
75+ if addressData .Scope == "" {
76+ addressData .Scope = DefaultScope
77+ }
78+
7379 defer func () {
7480 if err == errors .ErrTimeout {
75- context .timeoutCount .Add (1 )
81+ context .timeoutCount [ addressData . Scope ] .Add (1 )
7682 }
7783 }()
7884
@@ -94,21 +100,21 @@ func (context *Context) Run(addressData RunAddressData) (res Result, err error)
94100
95101 runTimer .Start ()
96102 defer func () {
97- context .metrics .add (wafRunTag , runTimer .Stop ())
98- context .metrics .merge (runTimer .Stats ())
103+ context .metrics .add (addressData . Scope , wafRunTag , runTimer .Stop ())
104+ context .metrics .merge (addressData . Scope , runTimer .Stats ())
99105 }()
100106
101107 wafEncodeTimer := runTimer .MustLeaf (wafEncodeTag )
102108 wafEncodeTimer .Start ()
103- persistentData , persistentEncoder , err := context .encodeOneAddressType (addressData .Persistent , wafEncodeTimer )
109+ persistentData , persistentEncoder , err := context .encodeOneAddressType (addressData .Scope , addressData . Persistent , wafEncodeTimer )
104110 if err != nil {
105111 wafEncodeTimer .Stop ()
106112 return res , err
107113 }
108114
109115 // The WAF releases ephemeral address data at the max of each run call, so we need not keep the Go values live beyond
110116 // that in the same way we need for persistent data. We hence use a separate encoder.
111- ephemeralData , ephemeralEncoder , err := context .encodeOneAddressType (addressData .Ephemeral , wafEncodeTimer )
117+ ephemeralData , ephemeralEncoder , err := context .encodeOneAddressType (addressData .Scope , addressData . Ephemeral , wafEncodeTimer )
112118 if err != nil {
113119 wafEncodeTimer .Stop ()
114120 return res , err
@@ -180,7 +186,7 @@ func merge[K comparable, V any](a, b map[K][]V) (merged map[K][]V) {
180186// is a nil map, but this behaviour is expected since either persistent or ephemeral addresses are allowed to be null
181187// one at a time. In this case, Encode will return nil contrary to Encode which will return a nil wafObject,
182188// which is what we need to send to ddwaf_run to signal that the address data is empty.
183- func (context * Context ) encodeOneAddressType (addressData map [string ]any , timer timer.Timer ) (* bindings.WafObject , encoder , error ) {
189+ func (context * Context ) encodeOneAddressType (scope Scope , addressData map [string ]any , timer timer.Timer ) (* bindings.WafObject , encoder , error ) {
184190 encoder := newLimitedEncoder (timer )
185191 if addressData == nil {
186192 return nil , encoder , nil
@@ -191,7 +197,7 @@ func (context *Context) encodeOneAddressType(addressData map[string]any, timer t
191197 context .mutex .Lock ()
192198 defer context .mutex .Unlock ()
193199
194- context .truncations = merge (context .truncations , encoder .truncations )
200+ context .truncations [ scope ] = merge (context .truncations [ scope ] , encoder .truncations )
195201 }
196202
197203 if timer .Exhausted () {
@@ -269,14 +275,15 @@ func (context *Context) Close() {
269275
270276// TotalRuntime returns the cumulated WAF runtime across various run calls within the same WAF context.
271277// Returned time is in nanoseconds.
272- // Deprecated: use Timings instead
278+ // Deprecated: use Stats instead
273279func (context * Context ) TotalRuntime () (uint64 , uint64 ) {
274- return uint64 (context .metrics .get (wafRunTag )), uint64 (context .metrics .get (wafDurationTag ))
280+ return uint64 (context .metrics .get (DefaultScope , wafRunTag )), uint64 (context .metrics .get (DefaultScope , wafDurationTag ))
275281}
276282
277283// TotalTimeouts returns the cumulated amount of WAF timeouts across various run calls within the same WAF context.
284+ // Deprecated: use Stats instead
278285func (context * Context ) TotalTimeouts () uint64 {
279- return context .timeoutCount .Load ()
286+ return context .timeoutCount [ DefaultScope ] .Load ()
280287}
281288
282289// Stats returns the cumulative time spent in various parts of the WAF, all in nanoseconds
@@ -285,15 +292,36 @@ func (context *Context) Stats() Stats {
285292 context .mutex .Lock ()
286293 defer context .mutex .Unlock ()
287294
288- truncations := make (map [TruncationReason ][]int , len (context .truncations ))
289- for reason , counts := range context .truncations {
295+ truncations := make (map [TruncationReason ][]int , len (context .truncations [ DefaultScope ] ))
296+ for reason , counts := range context .truncations [ DefaultScope ] {
290297 truncations [reason ] = make ([]int , len (counts ))
291298 copy (truncations [reason ], counts )
292299 }
293300
301+ raspTruncations := make (map [TruncationReason ][]int , len (context .truncations [RASPScope ]))
302+ for reason , counts := range context .truncations [RASPScope ] {
303+ raspTruncations [reason ] = make ([]int , len (counts ))
304+ copy (raspTruncations [reason ], counts )
305+ }
306+
307+ var (
308+ timeoutDefault uint64
309+ timeoutRASP uint64
310+ )
311+
312+ if atomic , ok := context .timeoutCount [DefaultScope ]; ok {
313+ timeoutDefault = atomic .Load ()
314+ }
315+
316+ if atomic , ok := context .timeoutCount [RASPScope ]; ok {
317+ timeoutRASP = atomic .Load ()
318+ }
319+
294320 return Stats {
295- Timers : context .metrics .copy (),
296- TimeoutCount : context .timeoutCount .Load (),
297- Truncations : truncations ,
321+ Timers : context .metrics .timers (),
322+ TimeoutCount : timeoutDefault ,
323+ TimeoutRASPCount : timeoutRASP ,
324+ Truncations : truncations ,
325+ TruncationsRASP : raspTruncations ,
298326 }
299327}
0 commit comments