mirror of
https://github.com/netbirdio/netbird.git
synced 2026-07-17 12:09:58 +00:00
* fix flaky test around event aggregation: control time.Now() from the test Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * actually use passed in func to generate time Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> --------- Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io>
366 lines
12 KiB
Go
366 lines
12 KiB
Go
package store
|
|
|
|
import (
|
|
"math/rand"
|
|
"net/netip"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/netbirdio/netbird/client/internal/netflow/types"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
var random = rand.New(rand.NewSource(time.Now().UnixNano()))
|
|
|
|
func TestFlowAggregation(t *testing.T) {
|
|
var protocols = []types.Protocol{types.ICMP, types.ICMPv6, types.TCP, types.UDP}
|
|
var tests = []struct {
|
|
description string
|
|
addresses [][]netip.Addr
|
|
dstPort uint16
|
|
eventTypes []types.Type
|
|
}{
|
|
{
|
|
description: "start and stop",
|
|
addresses: [][]netip.Addr{{netip.MustParseAddr("1.1.1.1"), netip.MustParseAddr("2.2.2.2")}, {netip.MustParseAddr("3.3.3.3"), netip.MustParseAddr("2.2.2.2")}},
|
|
dstPort: uint16(random.Uint32() >> 16),
|
|
eventTypes: []types.Type{types.TypeStart, types.TypeEnd},
|
|
},
|
|
{
|
|
description: "start and drop",
|
|
addresses: [][]netip.Addr{{netip.MustParseAddr("1.1.1.1"), netip.MustParseAddr("2.2.2.2")}, {netip.MustParseAddr("3.3.3.3"), netip.MustParseAddr("2.2.2.2")}},
|
|
dstPort: uint16(random.Uint32() >> 16),
|
|
eventTypes: []types.Type{types.TypeStart, types.TypeDrop},
|
|
},
|
|
{
|
|
description: "start only",
|
|
addresses: [][]netip.Addr{{netip.MustParseAddr("1.1.1.1"), netip.MustParseAddr("2.2.2.2")}, {netip.MustParseAddr("3.3.3.3"), netip.MustParseAddr("2.2.2.2")}},
|
|
dstPort: uint16(random.Uint32() >> 16),
|
|
eventTypes: []types.Type{types.TypeStart},
|
|
},
|
|
{
|
|
description: "drop only",
|
|
addresses: [][]netip.Addr{{netip.MustParseAddr("1.1.1.1"), netip.MustParseAddr("2.2.2.2")}, {netip.MustParseAddr("3.3.3.3"), netip.MustParseAddr("2.2.2.2")}},
|
|
dstPort: uint16(random.Uint32() >> 16),
|
|
eventTypes: []types.Type{types.TypeDrop},
|
|
}}
|
|
|
|
for _, protocol := range protocols {
|
|
for _, tt := range tests {
|
|
t.Run(tt.description+" "+protocol.String(), func(t *testing.T) {
|
|
store := NewAggregatingMemoryStore()
|
|
store.WindowEnd = time.Now().Add(5 * time.Second)
|
|
|
|
allExpected := make([]*types.Event, 0)
|
|
|
|
for _, srcAndDst := range tt.addresses {
|
|
inEvents, expected := generateEvents(srcAndDst[0], srcAndDst[1], tt.dstPort, tt.eventTypes, protocol, types.Ingress, 0, store.WindowStart, store.WindowEnd)
|
|
for _, e := range inEvents {
|
|
store.StoreEvent(e)
|
|
}
|
|
allExpected = append(allExpected, expected)
|
|
}
|
|
|
|
events := store.GetAggregatedEvents()
|
|
assert.ElementsMatch(t, events, allExpected)
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestIcmpEventAggregation(t *testing.T) {
|
|
var protocols = []types.Protocol{types.ICMP, types.ICMPv6}
|
|
var icmpTypes = []uint8{1, 2, 3}
|
|
|
|
var tests = []struct {
|
|
description string
|
|
addresses [][]netip.Addr
|
|
eventTypes []types.Type
|
|
}{
|
|
{
|
|
description: "start and stop",
|
|
addresses: [][]netip.Addr{{netip.MustParseAddr("1.1.1.1"), netip.MustParseAddr("2.2.2.2")}},
|
|
eventTypes: []types.Type{types.TypeStart, types.TypeEnd},
|
|
},
|
|
{
|
|
description: "start and drop",
|
|
addresses: [][]netip.Addr{{netip.MustParseAddr("1.1.1.1"), netip.MustParseAddr("2.2.2.2")}},
|
|
eventTypes: []types.Type{types.TypeStart, types.TypeDrop},
|
|
},
|
|
{
|
|
description: "start only",
|
|
addresses: [][]netip.Addr{{netip.MustParseAddr("1.1.1.1"), netip.MustParseAddr("2.2.2.2")}},
|
|
eventTypes: []types.Type{types.TypeStart},
|
|
},
|
|
{
|
|
description: "drop only",
|
|
addresses: [][]netip.Addr{{netip.MustParseAddr("1.1.1.1"), netip.MustParseAddr("2.2.2.2")}},
|
|
eventTypes: []types.Type{types.TypeDrop},
|
|
}}
|
|
|
|
for _, protocol := range protocols {
|
|
for _, tt := range tests {
|
|
t.Run(tt.description+" "+protocol.String(), func(t *testing.T) {
|
|
store := NewAggregatingMemoryStore()
|
|
store.WindowEnd = time.Now().Add(5 * time.Second)
|
|
|
|
allExpected := make([]*types.Event, 0)
|
|
for _, icmpType := range icmpTypes {
|
|
events, expected := generateEvents(tt.addresses[0][0], tt.addresses[0][1], 0, tt.eventTypes, protocol, types.Ingress, icmpType, store.WindowStart, store.WindowEnd)
|
|
for _, e := range events {
|
|
store.StoreEvent(e)
|
|
}
|
|
allExpected = append(allExpected, expected)
|
|
}
|
|
aggregatedEvents := store.GetAggregatedEvents()
|
|
assert.Len(t, aggregatedEvents, len(allExpected))
|
|
assert.ElementsMatch(t, aggregatedEvents, allExpected)
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFlowAggregationOfUnknownProtocols(t *testing.T) {
|
|
var tests = []struct {
|
|
description string
|
|
addresses [][]netip.Addr
|
|
dstPort uint16
|
|
eventTypes []types.Type
|
|
}{
|
|
{
|
|
description: "start and stop",
|
|
addresses: [][]netip.Addr{{netip.MustParseAddr("1.1.1.1"), netip.MustParseAddr("2.2.2.2")}, {netip.MustParseAddr("3.3.3.3"), netip.MustParseAddr("2.2.2.2")}},
|
|
dstPort: uint16(random.Uint32() >> 16),
|
|
eventTypes: []types.Type{types.TypeStart, types.TypeEnd},
|
|
},
|
|
{
|
|
description: "start and drop",
|
|
addresses: [][]netip.Addr{{netip.MustParseAddr("1.1.1.1"), netip.MustParseAddr("2.2.2.2")}, {netip.MustParseAddr("3.3.3.3"), netip.MustParseAddr("2.2.2.2")}},
|
|
dstPort: uint16(random.Uint32() >> 16),
|
|
eventTypes: []types.Type{types.TypeStart, types.TypeDrop},
|
|
},
|
|
{
|
|
description: "start only",
|
|
addresses: [][]netip.Addr{{netip.MustParseAddr("1.1.1.1"), netip.MustParseAddr("2.2.2.2")}, {netip.MustParseAddr("3.3.3.3"), netip.MustParseAddr("2.2.2.2")}},
|
|
dstPort: uint16(random.Uint32() >> 16),
|
|
eventTypes: []types.Type{types.TypeStart},
|
|
},
|
|
{
|
|
description: "drop only",
|
|
addresses: [][]netip.Addr{{netip.MustParseAddr("1.1.1.1"), netip.MustParseAddr("2.2.2.2")}, {netip.MustParseAddr("3.3.3.3"), netip.MustParseAddr("2.2.2.2")}},
|
|
dstPort: uint16(random.Uint32() >> 16),
|
|
eventTypes: []types.Type{types.TypeDrop},
|
|
}}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.description+" "+types.ProtocolUnknown.String(), func(t *testing.T) {
|
|
store := NewAggregatingMemoryStore()
|
|
store.WindowEnd = time.Now().Add(5 * time.Second)
|
|
|
|
allExpected := make([]*types.Event, 0)
|
|
|
|
for _, srcAndDst := range tt.addresses {
|
|
inEvents, expected := generateEventsForUnknownProtocol(srcAndDst[0], srcAndDst[1], tt.dstPort, tt.eventTypes, types.ProtocolUnknown, types.Ingress, store.WindowStart, store.WindowEnd)
|
|
for _, e := range inEvents {
|
|
store.StoreEvent(e)
|
|
}
|
|
allExpected = append(allExpected, expected...)
|
|
}
|
|
|
|
events := store.GetAggregatedEvents()
|
|
assert.ElementsMatch(t, events, allExpected)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestResetAggregationWindow(t *testing.T) {
|
|
now := time.Now()
|
|
nowFunc := func() time.Time { return now }
|
|
store := NewAggregatingMemoryStoreWithTimeFunc(nowFunc)
|
|
store.StoreEvent(&types.Event{
|
|
ID: uuid.New(),
|
|
Timestamp: time.Now(),
|
|
EventFields: types.EventFields{
|
|
FlowID: uuid.New(),
|
|
Type: types.TypeStart,
|
|
Protocol: types.TCP,
|
|
RuleID: []byte("rule-id-1"),
|
|
Direction: types.Ingress,
|
|
SourceIP: netip.MustParseAddr("1.1.1.1"),
|
|
SourcePort: 1234,
|
|
DestIP: netip.MustParseAddr("2.2.2.2"),
|
|
DestPort: 5678,
|
|
SourceResourceID: []byte("source-resource-id"),
|
|
DestResourceID: []byte("dest-resource-id"),
|
|
RxPackets: random.Uint64(),
|
|
TxPackets: random.Uint64(),
|
|
RxBytes: random.Uint64(),
|
|
TxBytes: random.Uint64(),
|
|
},
|
|
})
|
|
|
|
now = now.Add(1 * time.Second)
|
|
reset := store.ResetAggregationWindow()
|
|
previousEvents, ok := reset.(*AggregatingMemory)
|
|
assert.True(t, ok)
|
|
assert.NotEqual(t, previousEvents.WindowStart, store.WindowStart)
|
|
assert.Equal(t, previousEvents.WindowEnd, store.WindowStart)
|
|
assert.NotEmpty(t, previousEvents.events)
|
|
assert.Empty(t, store.events)
|
|
}
|
|
|
|
func generateEvents(srcIp, dstIp netip.Addr, dstPort uint16, eventTypes []types.Type, protocol types.Protocol,
|
|
direction types.Direction, icmpType uint8, windowStart, windowEnd time.Time) ([]*types.Event, *types.Event) {
|
|
var rxPackets, txPackets, rxBytes, txBytes uint64
|
|
inEvents := make([]*types.Event, 0)
|
|
ts := time.Now()
|
|
flowId := uuid.New()
|
|
srcPort := uint16(random.Uint32() >> 16)
|
|
|
|
for idx, eventType := range eventTypes {
|
|
e := &types.Event{
|
|
ID: uuid.New(),
|
|
Timestamp: ts.Add(time.Duration(idx) * time.Second),
|
|
EventFields: types.EventFields{
|
|
FlowID: flowId,
|
|
Type: eventType,
|
|
Protocol: protocol,
|
|
RuleID: []byte("rule-id-1"),
|
|
Direction: direction,
|
|
SourceIP: srcIp,
|
|
SourcePort: srcPort,
|
|
DestIP: dstIp,
|
|
DestPort: dstPort,
|
|
SourceResourceID: []byte("source-resource-id"),
|
|
DestResourceID: []byte("dest-resource-id"),
|
|
RxPackets: random.Uint64(),
|
|
TxPackets: random.Uint64(),
|
|
RxBytes: random.Uint64(),
|
|
TxBytes: random.Uint64(),
|
|
}}
|
|
rxBytes += e.RxBytes
|
|
txBytes += e.TxBytes
|
|
rxPackets += e.RxPackets
|
|
txPackets += e.TxPackets
|
|
inEvents = append(inEvents, e)
|
|
if protocol == types.ICMP || protocol == types.ICMPv6 {
|
|
e.ICMPType = icmpType
|
|
}
|
|
}
|
|
|
|
var start, end, drop uint64
|
|
for _, eventType := range eventTypes {
|
|
switch eventType {
|
|
case types.TypeStart:
|
|
start += 1
|
|
case types.TypeDrop:
|
|
drop += 1
|
|
case types.TypeEnd:
|
|
end += 1
|
|
}
|
|
}
|
|
aggregatedEvent := &types.Event{
|
|
ID: inEvents[0].ID,
|
|
Timestamp: inEvents[0].Timestamp,
|
|
WindowStart: windowStart,
|
|
WindowEnd: windowEnd,
|
|
EventFields: types.EventFields{
|
|
FlowID: flowId,
|
|
Type: types.TypeUnknown,
|
|
Protocol: inEvents[0].Protocol,
|
|
RuleID: []byte("rule-id-1"),
|
|
Direction: inEvents[0].Direction,
|
|
SourceIP: srcIp,
|
|
SourcePort: srcPort,
|
|
DestIP: dstIp,
|
|
DestPort: dstPort,
|
|
SourceResourceID: []byte("source-resource-id"),
|
|
DestResourceID: []byte("dest-resource-id"),
|
|
RxPackets: rxPackets,
|
|
TxPackets: txPackets,
|
|
RxBytes: rxBytes,
|
|
TxBytes: txBytes,
|
|
NumOfStarts: start,
|
|
NumOfEnds: end,
|
|
NumOfDrops: drop,
|
|
}}
|
|
if protocol == types.ICMP || protocol == types.ICMPv6 {
|
|
aggregatedEvent.ICMPType = icmpType
|
|
}
|
|
|
|
return inEvents, aggregatedEvent
|
|
}
|
|
|
|
func generateEventsForUnknownProtocol(srcIp, dstIp netip.Addr, dstPort uint16, eventTypes []types.Type, protocol types.Protocol,
|
|
direction types.Direction, windowStart, windowEnd time.Time) ([]*types.Event, []*types.Event) {
|
|
inEvents := make([]*types.Event, 0)
|
|
expectedEvents := make([]*types.Event, 0)
|
|
|
|
ts := time.Now()
|
|
flowId := uuid.New()
|
|
srcPort := uint16(random.Uint32() >> 16)
|
|
|
|
for idx, eventType := range eventTypes {
|
|
e := &types.Event{
|
|
ID: uuid.New(),
|
|
Timestamp: ts.Add(time.Duration(idx) * time.Second),
|
|
EventFields: types.EventFields{
|
|
FlowID: flowId,
|
|
Type: eventType,
|
|
Protocol: protocol,
|
|
RuleID: []byte("rule-id-1"),
|
|
Direction: direction,
|
|
SourceIP: srcIp,
|
|
SourcePort: srcPort,
|
|
DestIP: dstIp,
|
|
DestPort: dstPort,
|
|
SourceResourceID: []byte("source-resource-id"),
|
|
DestResourceID: []byte("dest-resource-id"),
|
|
RxPackets: random.Uint64(),
|
|
TxPackets: random.Uint64(),
|
|
RxBytes: random.Uint64(),
|
|
TxBytes: random.Uint64(),
|
|
}}
|
|
inEvents = append(inEvents, e)
|
|
|
|
var start, end, drop uint64
|
|
switch eventType {
|
|
case types.TypeStart:
|
|
start = 1
|
|
case types.TypeDrop:
|
|
drop = 1
|
|
case types.TypeEnd:
|
|
end = 1
|
|
}
|
|
|
|
expectedEvents = append(expectedEvents, &types.Event{
|
|
ID: e.ID,
|
|
Timestamp: e.Timestamp,
|
|
WindowStart: windowStart,
|
|
WindowEnd: windowEnd,
|
|
EventFields: types.EventFields{
|
|
FlowID: flowId,
|
|
Type: types.TypeUnknown,
|
|
Protocol: e.Protocol,
|
|
RuleID: []byte("rule-id-1"),
|
|
Direction: e.Direction,
|
|
SourceIP: srcIp,
|
|
SourcePort: srcPort,
|
|
DestIP: dstIp,
|
|
DestPort: dstPort,
|
|
SourceResourceID: []byte("source-resource-id"),
|
|
DestResourceID: []byte("dest-resource-id"),
|
|
RxPackets: e.RxPackets,
|
|
TxPackets: e.TxPackets,
|
|
RxBytes: e.RxBytes,
|
|
TxBytes: e.TxBytes,
|
|
NumOfStarts: start,
|
|
NumOfEnds: end,
|
|
NumOfDrops: drop,
|
|
}})
|
|
}
|
|
|
|
return inEvents, expectedEvents
|
|
}
|