Skip to content

Commit a8d625f

Browse files
committed
feat(filter): Evaluate sequences with multiple links
1 parent 6021974 commit a8d625f

File tree

3 files changed

+152
-75
lines changed

3 files changed

+152
-75
lines changed

pkg/filter/filter.go

Lines changed: 70 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ import (
3232
"github.com/rabbitstack/fibratus/pkg/event"
3333
"github.com/rabbitstack/fibratus/pkg/filter/fields"
3434
"github.com/rabbitstack/fibratus/pkg/filter/ql"
35-
"github.com/rabbitstack/fibratus/pkg/util/hashers"
3635
)
3736

3837
var (
@@ -207,12 +206,17 @@ func (f *filter) Compile() error {
207206
ql.WalkFunc(f.expr, walk)
208207
} else {
209208
if f.seq.By != nil {
210-
f.addField(f.seq.By)
209+
for _, fld := range f.seq.By.Fields {
210+
f.addField(fld)
211+
}
211212
}
212213
for _, expr := range f.seq.Expressions {
213214
ql.WalkFunc(expr.Expr, walk)
214-
if expr.By != nil {
215-
f.addField(expr.By)
215+
if expr.By == nil {
216+
continue
217+
}
218+
for _, fld := range expr.By.Fields {
219+
f.addField(fld)
216220
}
217221
}
218222
}
@@ -302,23 +306,67 @@ func (f *filter) evalBoundSequence(
302306
// evaluate the expression with the current valuer state
303307
if ql.Eval(expr.Expr, valuer, f.hasFunctions) {
304308
// compute sequence key hash to stich events
305-
hash := make([]byte, 0)
309+
values := make([]any, 0)
306310
for _, fld := range flds {
307311
if !strings.HasPrefix(fld.BoundVar, "$") {
308312
continue
309313
}
310-
hash = appendHash(hash, valuer[fld.Value])
314+
values = append(values, valuer[fld.Value])
311315
}
312-
fnv := hashers.FnvUint64(hash)
313-
e.AddSequenceLink(fnv)
314-
evt.AddSequenceLink(fnv)
316+
hash := hashFields(values)
317+
e.AddSequenceLink(hash)
318+
evt.AddSequenceLink(hash)
315319
return true
316320
}
317321
}
318322

319323
return false
320324
}
321325

326+
// evalSequence evaluates the sequence with one, multiple or
327+
// no join links. The sequence link is first consulted for the
328+
// global sequence definition, and if it is not defined then
329+
// the expression sequence link is used.
330+
func (f *filter) evalSequence(
331+
e *event.Event,
332+
seqID int,
333+
expr *ql.SequenceExpr,
334+
partials map[int][]*event.Event,
335+
valuer ql.MapValuer,
336+
) bool {
337+
// top-level sequence link is defined
338+
by := f.seq.By
339+
if by == nil {
340+
// otherwise, use the expression link
341+
by = expr.By
342+
}
343+
344+
var match bool
345+
if seqID >= 1 && by != nil {
346+
linkID := makeSequenceLinkID(valuer, by)
347+
// traverse upstream partials for join equality
348+
joins := make([]bool, seqID)
349+
outer:
350+
for i := range seqID {
351+
for _, p := range partials[i] {
352+
if CompareSeqLink(linkID, p.SequenceLinks()) {
353+
joins[i] = true
354+
continue outer
355+
}
356+
}
357+
}
358+
match = joinsEqual(joins) && ql.Eval(expr.Expr, valuer, f.hasFunctions)
359+
} else {
360+
match = ql.Eval(expr.Expr, valuer, f.hasFunctions)
361+
}
362+
363+
if match && by != nil {
364+
e.AddSequenceLink(makeSequenceLinkID(valuer, by))
365+
}
366+
367+
return match
368+
}
369+
322370
func (f *filter) RunSequence(e *event.Event, seqID int, partials map[int][]*event.Event, rawMatch bool) bool {
323371
if f.seq == nil {
324372
return false
@@ -343,45 +391,10 @@ func (f *filter) RunSequence(e *event.Event, seqID int, partials map[int][]*even
343391
match = f.evalBoundSequence(e, seqID, &expr, partials, valuer)
344392
} else {
345393
// evaluate constrained/unconstrained sequences
346-
by := f.seq.By
347-
if by == nil {
348-
by = expr.By
349-
}
350-
351-
if seqID >= 1 && by != nil {
352-
// traverse upstream partials for join equality
353-
joins := make([]bool, seqID)
354-
joinID := valuer[by.Value]
355-
outer:
356-
for i := range seqID {
357-
for _, p := range partials[i] {
358-
if CompareSeqLink(joinID, p.SequenceLinks()) {
359-
joins[i] = true
360-
continue outer
361-
}
362-
}
363-
}
364-
match = joinsEqual(joins) && ql.Eval(expr.Expr, valuer, f.hasFunctions)
365-
} else {
366-
match = ql.Eval(expr.Expr, valuer, f.hasFunctions)
367-
}
368-
369-
if match && by != nil {
370-
if v := valuer[by.Value]; v != nil {
371-
e.AddSequenceLink(v)
372-
}
373-
}
394+
match = f.evalSequence(e, seqID, &expr, partials, valuer)
374395
}
375-
return match
376-
}
377396

378-
func joinsEqual(joins []bool) bool {
379-
for _, j := range joins {
380-
if !j {
381-
return false
382-
}
383-
}
384-
return true
397+
return match
385398
}
386399

387400
func (f *filter) GetStringFields() map[fields.Field][]string { return f.stringFields }
@@ -564,3 +577,14 @@ func (f *filter) checkBoundRefs() error {
564577

565578
return nil
566579
}
580+
581+
func makeSequenceLinkID(valuer ql.MapValuer, link *ql.SequenceLink) any {
582+
if !link.IsCompound() {
583+
return valuer[link.First()]
584+
}
585+
values := make([]any, 0, len(link.Fields))
586+
for _, fld := range link.Fields {
587+
values = append(values, valuer[fld.Value])
588+
}
589+
return hashFields(values)
590+
}

pkg/filter/filter_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
package filter
2020

2121
import (
22+
"fmt"
2223
"net"
2324
"os"
2425
"path/filepath"
@@ -33,6 +34,7 @@ import (
3334
"github.com/rabbitstack/fibratus/pkg/event"
3435
"github.com/rabbitstack/fibratus/pkg/event/params"
3536
"github.com/rabbitstack/fibratus/pkg/filter/fields"
37+
"github.com/rabbitstack/fibratus/pkg/filter/ql"
3638
"github.com/rabbitstack/fibratus/pkg/fs"
3739
"github.com/rabbitstack/fibratus/pkg/pe"
3840
"github.com/rabbitstack/fibratus/pkg/ps"
@@ -110,6 +112,45 @@ func TestStringFields(t *testing.T) {
110112
assert.Len(t, f.GetStringFields()[fields.PsName], 1)
111113
}
112114

115+
func TestMakeSequenceLinkID(t *testing.T) {
116+
var tests = []struct {
117+
valuer ql.MapValuer
118+
seqLink *ql.SequenceLink
119+
id any
120+
}{
121+
{ql.MapValuer{
122+
"ps.uuid": uint64(123232454234232132),
123+
"ps.exe": "C:\\Windows\\System32\\cmd.exe"},
124+
&ql.SequenceLink{Fields: []*ql.FieldLiteral{{Value: "ps.exe"}, {Value: "ps.uuid"}}},
125+
"433a5c57696e646f77735c53797374656d33325c636d642e65786544556ea343cfb501",
126+
},
127+
{ql.MapValuer{
128+
"ps.uuid": uint64(123232454234232132),
129+
"module.address": uint64(0xfff32343)},
130+
&ql.SequenceLink{Fields: []*ql.FieldLiteral{{Value: "ps.uuid"}, {Value: "module.address"}}},
131+
"44556ea343cfb5014323f3ff00000000",
132+
},
133+
{ql.MapValuer{
134+
"ps.uuid": uint64(123232454234232132),
135+
"ps.exe": "C:\\Windows\\System32\\cmd.exe"},
136+
&ql.SequenceLink{Fields: []*ql.FieldLiteral{{Value: "ps.exe"}}},
137+
"C:\\Windows\\System32\\cmd.exe",
138+
},
139+
{ql.MapValuer{
140+
"ps.uuid": uint64(123232454234232132),
141+
"ps.exe": "C:\\Windows\\System32\\cmd.exe"},
142+
&ql.SequenceLink{Fields: []*ql.FieldLiteral{{Value: "ps.uuid"}}},
143+
uint64(123232454234232132),
144+
},
145+
}
146+
147+
for _, tt := range tests {
148+
t.Run(fmt.Sprintf("%v", tt.valuer), func(t *testing.T) {
149+
assert.Equal(t, tt.id, makeSequenceLinkID(tt.valuer, tt.seqLink))
150+
})
151+
}
152+
}
153+
113154
func TestProcFilter(t *testing.T) {
114155
parent := &pstypes.PS{
115156
Name: "svchost.exe",

pkg/filter/util.go

Lines changed: 41 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
package filter
2020

2121
import (
22+
"encoding/hex"
2223
"net"
2324
"path/filepath"
2425
"strings"
@@ -208,34 +209,45 @@ func compareSeqLink(lhs any, rhs any) bool {
208209
return false
209210
}
210211

211-
// appendHash appends the value's hashable bytes to buf.
212-
func appendHash(buf []byte, v any) []byte {
213-
switch val := v.(type) {
214-
case uint8:
215-
return append(buf, val)
216-
case uint16:
217-
return append(buf, bytes.WriteUint16(val)...)
218-
case uint32:
219-
return append(buf, bytes.WriteUint32(val)...)
220-
case uint64:
221-
return append(buf, bytes.WriteUint64(val)...)
222-
case int8:
223-
return append(buf, byte(val))
224-
case int16:
225-
return append(buf, bytes.WriteUint16(uint16(val))...)
226-
case int32:
227-
return append(buf, bytes.WriteUint32(uint32(val))...)
228-
case int64:
229-
return append(buf, bytes.WriteUint64(uint64(val))...)
230-
case int:
231-
return append(buf, bytes.WriteUint64(uint64(val))...)
232-
case uint:
233-
return append(buf, bytes.WriteUint64(uint64(val))...)
234-
case string:
235-
return append(buf, val...)
236-
case net.IP:
237-
return append(buf, val...)
238-
default:
239-
return buf
212+
// hashFields computes the hash of all field values.
213+
func hashFields(values []any) string {
214+
buf := make([]byte, 0)
215+
for _, v := range values {
216+
switch val := v.(type) {
217+
case uint8:
218+
buf = append(buf, val)
219+
case uint16:
220+
buf = append(buf, bytes.WriteUint16(val)...)
221+
case uint32:
222+
buf = append(buf, bytes.WriteUint32(val)...)
223+
case uint64:
224+
buf = append(buf, bytes.WriteUint64(val)...)
225+
case int8:
226+
buf = append(buf, byte(val))
227+
case int16:
228+
buf = append(buf, bytes.WriteUint16(uint16(val))...)
229+
case int32:
230+
buf = append(buf, bytes.WriteUint32(uint32(val))...)
231+
case int64:
232+
buf = append(buf, bytes.WriteUint64(uint64(val))...)
233+
case int:
234+
buf = append(buf, bytes.WriteUint64(uint64(val))...)
235+
case uint:
236+
buf = append(buf, bytes.WriteUint64(uint64(val))...)
237+
case string:
238+
buf = append(buf, val...)
239+
case net.IP:
240+
buf = append(buf, val...)
241+
}
242+
}
243+
return hex.EncodeToString(buf)
244+
}
245+
246+
func joinsEqual(joins []bool) bool {
247+
for _, j := range joins {
248+
if !j {
249+
return false
250+
}
240251
}
252+
return true
241253
}

0 commit comments

Comments
 (0)