Files
akvorado/demoexporter/flows/generate.go
2025-10-26 12:14:20 +01:00

159 lines
4.9 KiB
Go

// SPDX-FileCopyrightText: 2022 Free Mobile
// SPDX-License-Identifier: AGPL-3.0-only
package flows
import (
"fmt"
"hash/fnv"
"math"
"math/bits"
"math/rand/v2"
"net/netip"
"time"
"akvorado/common/helpers"
)
// GeneratedFlow represents a generated flow.
type generatedFlow struct {
IPFlow
EType uint16
SrcAddr netip.Addr
DstAddr netip.Addr
}
// rateToCount converts a per-second rate to the number of items to
// produce for the given time.
func rateToCount(rate float64, now time.Time) int {
seconds := float64(now.Unix() - now.Truncate(time.Hour*24*30*12).Unix())
count := math.Trunc((seconds+1)*rate) - math.Trunc(seconds*rate)
return int(count)
}
// randomIP returns a random IP in the provided prefix.
func randomIP(prefix netip.Prefix, r *rand.Rand) netip.Addr {
result := make([]byte, prefix.Addr().BitLen()/8)
for i := range result {
if prefix.Bits() >= (i+1)*8 {
result[i] = prefix.Addr().AsSlice()[i]
continue
}
shiftMask := max(0, prefix.Bits()-i*8)
randomByte := byte(r.IntN(256))
randomByte = randomByte & ^bits.Reverse8(byte((1<<shiftMask)-1))
result[i] = randomByte | prefix.Addr().AsSlice()[i]
}
addr, _ := netip.AddrFromSlice(result)
return addr
}
// peakHourDistance returns distance from peak hour (0 to 1)
func peakHourDistance(now, peak time.Duration) float64 {
delta := math.Mod(math.Abs((now - peak).Hours()), 24)
if 24-delta < delta {
delta = 24 - delta
}
return (12 - delta) / 12
}
// chooseRandom returns a random value from a slice
func chooseRandom[T any](r *rand.Rand, slice []T) T {
if len(slice) == 0 {
var result T
return result
}
if len(slice) == 1 {
return slice[0]
}
return slice[r.IntN(len(slice))]
}
// generateFlows generate a set of flows using the provided
// configuration, for the provided date. It returns one second worth
// of flows. This is stateless and not very efficient if we have many
// flow configurations.
func generateFlows(flowConfigs []FlowConfiguration, seed int64, now time.Time) []generatedFlow {
flows := []generatedFlow{}
now = now.Truncate(time.Second)
// Initialize the random number generator to a known state
hash := fnv.New64()
fmt.Fprintf(hash, "%d %d", now.Unix(), seed)
r := rand.New(rand.NewPCG(hash.Sum64(), 0))
nowTime := now.Sub(now.Truncate(time.Hour * 24))
for _, flowConfig := range flowConfigs {
// Compute how many per seconds
distance := peakHourDistance(nowTime, flowConfig.PeakHour)
square := distance * distance
multiplier := 1 + (flowConfig.Multiplier-1)*square/(2.*(square-distance)+1.)
count := rateToCount(flowConfig.PerSecond*multiplier*(0.9+r.Float64()/5), now)
for ; count > 0; count-- {
flow := generatedFlow{
IPFlow: IPFlow{
Packets: 1,
InputInt: uint32(chooseRandom(r, flowConfig.InIfIndex)),
OutputInt: uint32(chooseRandom(r, flowConfig.OutIfIndex)),
SrcAS: chooseRandom(r, flowConfig.SrcAS),
DstAS: chooseRandom(r, flowConfig.DstAS),
ForwardStatus: 64,
},
}
if flowConfig.Size == 0 {
flow.Octets = uint32(r.Int32N(1200) + 300)
} else {
flow.Octets = uint32(float64(flowConfig.Size) * (r.NormFloat64()*0.3 + 1))
if flow.Octets > 9000 {
flow.Octets = 9000
} else if flow.Octets > 1500 && flowConfig.Size <= 1500 {
flow.Octets = 1500
}
}
flow.SrcAddr = randomIP(flowConfig.SrcNet, r)
flow.SrcMask = uint8(flowConfig.SrcNet.Bits())
flow.DstAddr = randomIP(flowConfig.DstNet, r)
flow.DstMask = uint8(flowConfig.DstNet.Bits())
proto := chooseRandom(r, flowConfig.Protocol)
if proto == "tcp" || proto == "udp" {
if srcPort := chooseRandom(r, flowConfig.SrcPort); srcPort != 0 {
flow.SrcPort = srcPort
} else {
flow.SrcPort = uint16(r.Int32N(2000) + 33000)
}
if dstPort := chooseRandom(r, flowConfig.DstPort); dstPort != 0 {
flow.DstPort = dstPort
} else {
flow.DstPort = uint16(r.Int32N(2000) + 33000)
}
}
if flow.SrcAddr.Is4() {
flow.EType = helpers.ETypeIPv4
} else {
flow.EType = helpers.ETypeIPv6
}
if proto == "tcp" {
flow.Proto = 6
} else if proto == "udp" {
flow.Proto = 17
} else if proto == "icmp" && flow.EType == helpers.ETypeIPv4 {
flow.Proto = 1
} else if proto == "icmp" && flow.EType == helpers.ETypeIPv6 {
flow.Proto = 58
}
flows = append(flows, flow)
if flowConfig.ReverseDirectionRatio > 0 {
reverseFlow := flow
reverseFlow.Octets = uint32(float32(reverseFlow.Octets) * flowConfig.ReverseDirectionRatio)
reverseFlow.DstAS, reverseFlow.SrcAS = reverseFlow.SrcAS, reverseFlow.DstAS
reverseFlow.SrcAddr, reverseFlow.DstAddr = reverseFlow.DstAddr, reverseFlow.SrcAddr
reverseFlow.SrcMask, reverseFlow.DstMask = reverseFlow.DstMask, reverseFlow.SrcMask
reverseFlow.SrcPort, reverseFlow.DstPort = reverseFlow.DstPort, reverseFlow.SrcPort
reverseFlow.InputInt, reverseFlow.OutputInt = reverseFlow.OutputInt, reverseFlow.InputInt
flows = append(flows, reverseFlow)
}
}
}
return flows
}