mirror of
https://github.com/akvorado/akvorado.git
synced 2025-12-11 22:14:02 +01:00
303 lines
7.9 KiB
Go
303 lines
7.9 KiB
Go
// SPDX-FileCopyrightText: 2022 Free Mobile
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
package flows
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"math/rand/v2"
|
|
"net/netip"
|
|
"slices"
|
|
"testing"
|
|
"time"
|
|
|
|
"akvorado/common/helpers"
|
|
)
|
|
|
|
func TestRateToCount(t *testing.T) {
|
|
rates := []float64{0.2, 0.4, 0.6, 1, 1.4, 2.3, 2.8, 3, 3.2, 4.7, 1200}
|
|
now := time.Now().Truncate(time.Second)
|
|
for _, rate := range rates {
|
|
result := 0
|
|
for range 1000 {
|
|
result += rateToCount(rate, now)
|
|
now = now.Add(time.Second)
|
|
}
|
|
computedRate := float64(result) / 1000
|
|
if math.Abs(rate-computedRate) > rate*0.01 {
|
|
t.Errorf("rateToCount(%f) was %f on average", rate, computedRate)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRandomIP(t *testing.T) {
|
|
prefixes := []string{
|
|
"192.168.0.0/24",
|
|
"192.168.0.0/16",
|
|
"172.16.0.0/12",
|
|
"192.168.14.1/32",
|
|
"0.0.0.0/0",
|
|
"2001:db8::/32",
|
|
"2001:db8:a:b::/64",
|
|
"2001:db8:a:c:d::1/128",
|
|
}
|
|
r := rand.New(rand.NewPCG(0, 0))
|
|
for _, p := range prefixes {
|
|
prefix := netip.MustParsePrefix(p)
|
|
for range 1000 {
|
|
ip := randomIP(prefix, r)
|
|
if !prefix.Contains(ip) {
|
|
t.Errorf("randomIP(%q) == %q not in prefix", p, ip)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestPeakHourDistance(t *testing.T) {
|
|
cases := []struct {
|
|
Pos helpers.Pos
|
|
Peak time.Duration
|
|
Now time.Duration
|
|
Expected float64
|
|
}{
|
|
{helpers.Mark(), 6 * time.Hour, 6 * time.Hour, 1},
|
|
{helpers.Mark(), 6 * time.Hour, 0, 0.5},
|
|
{helpers.Mark(), 6 * time.Hour, 18 * time.Hour, 0},
|
|
{helpers.Mark(), 12 * time.Hour, 13 * time.Hour, 11. / 12},
|
|
{helpers.Mark(), 12 * time.Hour, 11 * time.Hour, 11. / 12},
|
|
{helpers.Mark(), 12 * time.Hour, 14 * time.Hour, 10. / 12},
|
|
{helpers.Mark(), 12 * time.Hour, 15 * time.Hour, 9. / 12},
|
|
{helpers.Mark(), 12 * time.Hour, 16 * time.Hour, 8. / 12},
|
|
{helpers.Mark(), 12 * time.Hour, 17 * time.Hour, 7. / 12},
|
|
{helpers.Mark(), 12 * time.Hour, 18 * time.Hour, 6. / 12},
|
|
{helpers.Mark(), 12 * time.Hour, 19 * time.Hour, 5. / 12},
|
|
}
|
|
for _, tc := range cases {
|
|
got := peakHourDistance(tc.Now, tc.Peak)
|
|
if math.Abs(got-tc.Expected) > tc.Expected*0.01 {
|
|
t.Errorf("%speakHourDistance(%s, %s) == %f, expected %f",
|
|
tc.Pos, tc.Peak, tc.Now, got, tc.Expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestChooseRandom(t *testing.T) {
|
|
cases := [][]int{
|
|
nil,
|
|
{},
|
|
{6},
|
|
{1, 2, 3, 4, 10, 12},
|
|
}
|
|
r := rand.New(rand.NewPCG(0, 0))
|
|
for _, tc := range cases {
|
|
t.Run(fmt.Sprintf("%v", tc), func(t *testing.T) {
|
|
results := map[int]bool{}
|
|
for range 100 {
|
|
result := chooseRandom(r, tc)
|
|
results[result] = true
|
|
if len(tc) == 0 {
|
|
if result != 0 {
|
|
t.Fatalf("chooseRandom() == %d instead of 0", result)
|
|
}
|
|
break
|
|
}
|
|
if !slices.Contains(tc, result) {
|
|
t.Fatalf("chooseRandom() returned %d, not in slice",
|
|
result)
|
|
}
|
|
}
|
|
if len(tc) != 0 && len(results) != len(tc) {
|
|
t.Fatalf("chooseRandom() did not explore all results (only %d)",
|
|
len(results))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGenerateFlows(t *testing.T) {
|
|
cases := []struct {
|
|
Pos helpers.Pos
|
|
FlowConfiguration
|
|
Expected []generatedFlow
|
|
}{
|
|
{
|
|
Pos: helpers.Mark(),
|
|
FlowConfiguration: FlowConfiguration{
|
|
PerSecond: 1,
|
|
InIfIndex: []int{10},
|
|
OutIfIndex: []int{20, 21},
|
|
PeakHour: 21 * time.Hour,
|
|
Multiplier: 3.1, // 6 hours from peak time → ~2
|
|
SrcNet: netip.MustParsePrefix("192.0.2.0/24"),
|
|
DstNet: netip.MustParsePrefix("203.0.113.0/24"),
|
|
SrcAS: []uint32{65201},
|
|
DstAS: []uint32{65202},
|
|
SrcPort: []uint16{443},
|
|
Protocol: []string{"tcp"},
|
|
Size: 1400,
|
|
},
|
|
Expected: []generatedFlow{
|
|
{
|
|
SrcAddr: netip.MustParseAddr("192.0.2.155"),
|
|
DstAddr: netip.MustParseAddr("203.0.113.59"),
|
|
EType: 0x800,
|
|
IPFlow: IPFlow{
|
|
Octets: 1011,
|
|
Packets: 1,
|
|
Proto: 6,
|
|
SrcPort: 443,
|
|
DstPort: 33550,
|
|
InputInt: 10,
|
|
OutputInt: 21,
|
|
SrcAS: 65201,
|
|
DstAS: 65202,
|
|
ForwardStatus: 64,
|
|
SrcMask: 24,
|
|
DstMask: 24,
|
|
},
|
|
}, {
|
|
SrcAddr: netip.MustParseAddr("192.0.2.3"),
|
|
DstAddr: netip.MustParseAddr("203.0.113.52"),
|
|
EType: 0x800,
|
|
IPFlow: IPFlow{
|
|
Octets: 1478,
|
|
Packets: 1,
|
|
Proto: 6,
|
|
SrcPort: 443,
|
|
DstPort: 33200,
|
|
InputInt: 10,
|
|
OutputInt: 20,
|
|
SrcAS: 65201,
|
|
DstAS: 65202,
|
|
ForwardStatus: 64,
|
|
SrcMask: 24,
|
|
DstMask: 24,
|
|
},
|
|
}, {
|
|
SrcAddr: netip.MustParseAddr("192.0.2.151"),
|
|
DstAddr: netip.MustParseAddr("203.0.113.245"),
|
|
EType: 2048,
|
|
IPFlow: IPFlow{
|
|
Packets: 1,
|
|
Octets: 1311,
|
|
Proto: 6,
|
|
InputInt: 10,
|
|
OutputInt: 20,
|
|
SrcPort: 443,
|
|
DstPort: 33389,
|
|
SrcAS: 65201,
|
|
DstAS: 65202,
|
|
ForwardStatus: 64,
|
|
SrcMask: 24,
|
|
DstMask: 24,
|
|
},
|
|
},
|
|
},
|
|
}, {
|
|
Pos: helpers.Mark(),
|
|
FlowConfiguration: FlowConfiguration{
|
|
PerSecond: 1,
|
|
InIfIndex: []int{20},
|
|
OutIfIndex: []int{10, 11},
|
|
PeakHour: 3 * time.Hour,
|
|
Multiplier: 4, // 12 hours from peak time → ~1
|
|
SrcNet: netip.MustParsePrefix("2001:db8::1/128"),
|
|
DstNet: netip.MustParsePrefix("2001:db8:2::/64"),
|
|
SrcAS: []uint32{65201},
|
|
DstAS: []uint32{65202},
|
|
DstPort: []uint16{443},
|
|
Protocol: []string{"tcp"},
|
|
Size: 1200,
|
|
},
|
|
Expected: []generatedFlow{
|
|
{
|
|
SrcAddr: netip.MustParseAddr("2001:db8::1"),
|
|
DstAddr: netip.MustParseAddr("2001:db8:2:0:9b3b:48ac:f003:34f4"),
|
|
EType: 0x86dd,
|
|
IPFlow: IPFlow{
|
|
Octets: 866,
|
|
Packets: 1,
|
|
Proto: 6,
|
|
SrcPort: 33820,
|
|
DstPort: 443,
|
|
InputInt: 20,
|
|
OutputInt: 11,
|
|
SrcAS: 65201,
|
|
DstAS: 65202,
|
|
ForwardStatus: 64,
|
|
SrcMask: 128,
|
|
DstMask: 64,
|
|
},
|
|
},
|
|
},
|
|
}, {
|
|
Pos: helpers.Mark(),
|
|
FlowConfiguration: FlowConfiguration{
|
|
PerSecond: 1,
|
|
InIfIndex: []int{20},
|
|
OutIfIndex: []int{10, 11},
|
|
PeakHour: 3 * time.Hour,
|
|
Multiplier: 4, // 12 hours from peak time → ~1
|
|
SrcNet: netip.MustParsePrefix("2001:db8::1/128"),
|
|
DstNet: netip.MustParsePrefix("2001:db8:2::/64"),
|
|
SrcAS: []uint32{65201},
|
|
DstAS: []uint32{65202},
|
|
DstPort: []uint16{443},
|
|
Protocol: []string{"tcp"},
|
|
Size: 1200,
|
|
ReverseDirectionRatio: 0.1,
|
|
},
|
|
Expected: []generatedFlow{
|
|
{
|
|
SrcAddr: netip.MustParseAddr("2001:db8::1"),
|
|
DstAddr: netip.MustParseAddr("2001:db8:2:0:9b3b:48ac:f003:34f4"),
|
|
EType: 0x86dd,
|
|
IPFlow: IPFlow{
|
|
Octets: 866,
|
|
Packets: 1,
|
|
Proto: 6,
|
|
SrcPort: 33820,
|
|
DstPort: 443,
|
|
InputInt: 20,
|
|
OutputInt: 11,
|
|
SrcAS: 65201,
|
|
DstAS: 65202,
|
|
ForwardStatus: 64,
|
|
SrcMask: 128,
|
|
DstMask: 64,
|
|
},
|
|
}, {
|
|
DstAddr: netip.MustParseAddr("2001:db8::1"),
|
|
SrcAddr: netip.MustParseAddr("2001:db8:2:0:9b3b:48ac:f003:34f4"),
|
|
EType: 0x86dd,
|
|
IPFlow: IPFlow{
|
|
Octets: 866 / 10,
|
|
Packets: 1,
|
|
Proto: 6,
|
|
DstPort: 33820,
|
|
SrcPort: 443,
|
|
OutputInt: 20,
|
|
InputInt: 11,
|
|
DstAS: 65201,
|
|
SrcAS: 65202,
|
|
ForwardStatus: 64,
|
|
DstMask: 128,
|
|
SrcMask: 64,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
now := time.Date(2022, 3, 18, 15, 0, 0, 0, time.UTC)
|
|
for _, tc := range cases {
|
|
t.Run(fmt.Sprintf("case %s", tc.Pos), func(t *testing.T) {
|
|
got := generateFlows([]FlowConfiguration{tc.FlowConfiguration}, 0, now)
|
|
if diff := helpers.Diff(got, tc.Expected); diff != "" {
|
|
t.Fatalf("%sgeneratedFlows() (-got, +want):\n%s", tc.Pos, diff)
|
|
}
|
|
})
|
|
}
|
|
}
|