From fee5aa711f275c4ca8a5a985dc332a84a25213d2 Mon Sep 17 00:00:00 2001 From: Alexandre Perrin Date: Tue, 24 Mar 2026 15:48:28 +0100 Subject: [PATCH 1/4] flow: add a simple benchmark test Signed-off-by: Alexandre Perrin --- flow/fake_test.go | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 flow/fake_test.go diff --git a/flow/fake_test.go b/flow/fake_test.go new file mode 100644 index 0000000..36f8096 --- /dev/null +++ b/flow/fake_test.go @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of Cilium + +package flow_test + +import ( + "runtime" + "sync" + "testing" + + fakeflow "github.com/cilium/fake/flow" +) + +func BenchmarkFakeFlow(b *testing.B) { + // run GOMAXPROCS goroutine, allowing for override when benchmarking while + // still being a sane default. + p := runtime.GOMAXPROCS(0) + n := (b.N / p) + 1 // round up + + var wg sync.WaitGroup + wg.Add(p) + for range p { + go func() { + for _ = range n { + _ = fakeflow.New() + } + wg.Done() + }() + } + wg.Wait() +} From eaef397489dc366b6e6eff6886fba66a1cd19df0 Mon Sep 17 00:00:00 2001 From: Alexandre Perrin Date: Wed, 3 Dec 2025 18:49:39 +0100 Subject: [PATCH 2/4] fake: provide a new struct API Before this patch, the fake library would only expose package defined methods. This had the limitation of scaling poorly when used by multiple goroutines (although both recent golang versions and rand/v2 addressed the scaling issues), and did not abstract the PRNG very well. This commit introduces a new faker struct, constructors, and struct methods matching the exposed API. Each faker struct has its own instance PRNG and should not be used concurrently. Additionally, a package level struct is created and used by package level defined functions for full retro-compatibility. The following commits will introduce the same change to the flow module, and adapt the cmd module to use the new API. Signed-off-by: Alexandre Perrin --- data.go | 1 + fake.go | 92 ++++++++++++++++++++++++++++++++++++++++++ k8s.go | 45 ++++++++------------- legacy.go | 115 +++++++++++++++++++++++++++++++++++++++++++++++++++++ names.go | 50 ++++++++++------------- network.go | 25 +++++------- 6 files changed, 258 insertions(+), 70 deletions(-) create mode 100644 fake.go create mode 100644 legacy.go diff --git a/data.go b/data.go index 59135e6..c337ab8 100644 --- a/data.go +++ b/data.go @@ -40,6 +40,7 @@ var apps = []string{ "wordpress", "zookeeper", } + var labels = []string{ "io.cilium/app", "k8s-app", diff --git a/fake.go b/fake.go new file mode 100644 index 0000000..7216d2d --- /dev/null +++ b/fake.go @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of Cilium + +package fake + +import ( + "encoding/binary" + "io" + randv2 "math/rand/v2" + "strings" +) + +// Faker is the main interface exposed to generate fake data. +type Faker interface { //nolint:interfacebloat + // Adjective generates a random adjective. + Adjective() string + // AlphaNum generates a random alphanumeric string of the given length. + AlphaNum(length int) string + // App generates a random software application name. + App() string + // Noun generates a random noun. + Noun() string + // Name generates a random name. + Name() string + // Names generates a random set of names. It panics if n < 0. + Names(n int) []string + // DeploymentTier generates a random software deployment tier such as prod, + // staging, etc. + DeploymentTier() string + + // K8sLabels generates a random set of Kubernetes labels. + K8sLabels() []string + // K8sNamespace generates a random Kubernetes namespace name. + K8sNamespace() string + // K8sNodeName generates a random Kubernetes node name. + K8sNodeName() string + // K8sPodName generates a random Kubernetes pod name. + K8sPodName() string + + // MAC generates a random MAC address. + MAC() string + // IP generates a random IP address. Options may be provided to specify a + // network for the address or if it should be IPv4 or IPv6. + IP(options ...IPOption) string + // Port generates a random port number between 1 and 65535 or in the range + // specified by the given option. + Port(options ...PortOption) uint32 +} + +// New creates a new Faker using a random seed. +func New() Faker { + // NOTE: We seed from the global math/rand/v2 source rather than + // crypto/rand to avoid errors handling; the faker should not be used for + // security-sensitive purposes. We use ChaCha8 rather than PCG as ChaCha8 + // implements io.Reader. + var seed [32]byte + for i := range 4 { + binary.LittleEndian.PutUint64(seed[i*8:], randv2.Uint64()) + } + return NewWithSource(randv2.NewChaCha8(seed)) +} + +// RandSourceReader is a random source that also implements io.Reader. +type RandSourceReader interface { + randv2.Source + io.Reader +} + +// NewWithSource creates a new Faker using the given random source. Useful to +// control the faker output, e.g. for testing. +func NewWithSource(src RandSourceReader) Faker { + return &faker{ + Rand: randv2.New(src), + Reader: src, + } +} + +// faker is a private struct implementing Faker. +type faker struct { + *randv2.Rand + io.Reader +} + +// join3 is a helper to build a string composed of three parts. +func join3(left, middle, right string) string { + var sb strings.Builder + sb.Grow(len(left) + len(middle) + len(right)) + sb.WriteString(left) + sb.WriteString(middle) + sb.WriteString(right) + return sb.String() +} diff --git a/k8s.go b/k8s.go index 6ef790d..c13bf73 100644 --- a/k8s.go +++ b/k8s.go @@ -3,44 +3,33 @@ package fake -import ( - "fmt" - "math/rand/v2" -) - -// K8sLabels generates a random set of Kubernetes labels. -func K8sLabels() []string { +// K8sLabels implements the Faker interface for faker. +func (f *faker) K8sLabels() []string { + // XXX: figure out if this is called often, we could init the slice with a + // len(labels) / 2 capacity. var l []string for _, name := range labels { - if rand.IntN(2) == 0 { // 50% chance of picking up this label - l = append(l, name+"="+App()) + if f.IntN(2) == 0 { // 50% chance of picking up this label + l = append(l, join3(name, "=", f.App())) } } return l } -// K8sNamespace generates a random Kubernetes namespace name. -func K8sNamespace() string { - if rand.IntN(2) == 0 { - return namespaces[rand.IntN(len(namespaces))] +// K8sNamespace implements the Faker interface for faker. +func (f *faker) K8sNamespace() string { + if f.IntN(2) == 0 { + return namespaces[f.IntN(len(namespaces))] } - return fmt.Sprintf("%s-%s", App(), DeploymentTier()) + return join3(f.App(), "-", f.DeploymentTier()) } -// K8sNodeName generates a random Kubernetes node name. -func K8sNodeName() string { - return fmt.Sprintf( - "%s-%s", - Adjective(), - Noun(), - ) +// K8sNodeName implements the Faker interface for faker. +func (f *faker) K8sNodeName() string { + return join3(f.Adjective(), "-", f.Noun()) } -// K8sPodName generates a random Kubernetes pod name. -func K8sPodName() string { - return fmt.Sprintf( - "%s-%s", - App(), - AlphaNum(5), - ) +// K8sPodName implements the Faker interface for faker. +func (f *faker) K8sPodName() string { + return join3(f.App(), "-", f.AlphaNum(5)) } diff --git a/legacy.go b/legacy.go new file mode 100644 index 0000000..af0a4c8 --- /dev/null +++ b/legacy.go @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of Cilium + +package fake + +import "sync" + +// globalFaker is used by the legacy API of package level functions. +var ( + globalFaker = New() + globalMu sync.Mutex +) + +// Legacy package-level functions + +// Adjective generates a random adjective. +func Adjective() string { + globalMu.Lock() + defer globalMu.Unlock() + return globalFaker.Adjective() +} + +// AlphaNum generates a random alphanumeric string of the given length. +func AlphaNum(length int) string { + globalMu.Lock() + defer globalMu.Unlock() + return globalFaker.AlphaNum(length) +} + +// App generates a random software application name. +func App() string { + globalMu.Lock() + defer globalMu.Unlock() + return globalFaker.App() +} + +// Noun generates a random noun. +func Noun() string { + globalMu.Lock() + defer globalMu.Unlock() + return globalFaker.Noun() +} + +// Name generates a random name. +func Name() string { + globalMu.Lock() + defer globalMu.Unlock() + return globalFaker.Name() +} + +// Names generates a random set of names. It panics if n < 0. +func Names(n int) []string { + globalMu.Lock() + defer globalMu.Unlock() + return globalFaker.Names(n) +} + +// DeploymentTier generates a random software deployment tier such as prod, +// staging, etc. +func DeploymentTier() string { + globalMu.Lock() + defer globalMu.Unlock() + return globalFaker.DeploymentTier() +} + +// MAC generates a random MAC address. +func MAC() string { + globalMu.Lock() + defer globalMu.Unlock() + return globalFaker.MAC() +} + +// IP generates a random IP address. Options may be provided to specify a +// network for the address or if it should be IPv4 or IPv6. +func IP(options ...IPOption) string { + globalMu.Lock() + defer globalMu.Unlock() + return globalFaker.IP(options...) +} + +// Port generates a random port number between 1 and 65535 or in the range +// specified by the given option. +func Port(options ...PortOption) uint32 { + globalMu.Lock() + defer globalMu.Unlock() + return globalFaker.Port(options...) +} + +// K8sLabels generates a random set of Kubernetes labels. +func K8sLabels() []string { + globalMu.Lock() + defer globalMu.Unlock() + return globalFaker.K8sLabels() +} + +// K8sNamespace generates a random Kubernetes namespace name. +func K8sNamespace() string { + globalMu.Lock() + defer globalMu.Unlock() + return globalFaker.K8sNamespace() +} + +// K8sNodeName generates a random Kubernetes node name. +func K8sNodeName() string { + globalMu.Lock() + defer globalMu.Unlock() + return globalFaker.K8sNodeName() +} + +// K8sPodName generates a random Kubernetes pod name. +func K8sPodName() string { + globalMu.Lock() + defer globalMu.Unlock() + return globalFaker.K8sPodName() +} diff --git a/names.go b/names.go index f50ea47..29571b0 100644 --- a/names.go +++ b/names.go @@ -3,52 +3,46 @@ package fake -import ( - "fmt" - "math/rand/v2" -) - -// Adjective generates a random adjective. -func Adjective() string { - return adjectives[rand.IntN(len(adjectives))] +// Adjective implements the Faker interface for faker. +func (f *faker) Adjective() string { + return adjectives[f.IntN(len(adjectives))] } -// AlphaNum generates a random alphanumeric string of the given length. -func AlphaNum(length int) string { +// AlphaNum implements the Faker interface for faker. +func (f *faker) AlphaNum(length int) string { b := make([]byte, length) for i := range b { - b[i] = alphanum[rand.IntN(len(alphanum))] + b[i] = alphanum[f.IntN(len(alphanum))] } return string(b) } -// App generates a random software application name. -func App() string { - return apps[rand.IntN(len(apps))] +// App implements the Faker interface for faker. +func (f *faker) App() string { + return apps[f.IntN(len(apps))] } -// Noun generates a random noun. -func Noun() string { - return nouns[rand.IntN(len(nouns))] +// Noun implements the Faker interface for faker. +func (f *faker) Noun() string { + return nouns[f.IntN(len(nouns))] } -// Name generates a random name. -func Name() string { - return fmt.Sprintf("%s_%s", Adjective(), Noun()) +// Name implements the Faker interface for faker. +func (f *faker) Name() string { + return join3(f.Adjective(), "_", f.Noun()) } -// Names generates a random set of names. It panics if n < 0. -func Names(n int) []string { - n = rand.IntN(n + 1) +// Names implements the Faker interface for faker. +func (f *faker) Names(n int) []string { + n = f.IntN(n + 1) names := make([]string, n) for i := range n { - names[i] = Name() + names[i] = f.Name() } return names } -// DeploymentTier generates a random software deployment tier such as prod, -// staging, etc. -func DeploymentTier() string { - return tiers[rand.IntN(len(tiers))] +// DeploymentTier implements the Faker interface for faker. +func (f *faker) DeploymentTier() string { + return tiers[f.IntN(len(tiers))] } diff --git a/network.go b/network.go index b3ea4c4..8f74d03 100644 --- a/network.go +++ b/network.go @@ -4,9 +4,7 @@ package fake import ( - crand "crypto/rand" "fmt" - "math/rand/v2" "net" ) @@ -14,10 +12,10 @@ import ( // support 6 bytes MAC. const macLen = 6 -// MAC generates a random MAC address. -func MAC() string { +// MAC implements the Faker interface for faker. +func (f *faker) MAC() string { hw := make(net.HardwareAddr, macLen) - _, _ = crand.Read(hw) + _, _ = f.Read(hw) return hw.String() } @@ -65,9 +63,8 @@ func WithIPCIDR(cidr string) IPOption { }) } -// IP generates a random IP address. Options may be provided to specify a -// network for the address or if it should be IPv4 or IPv6. -func IP(options ...IPOption) string { +// IP implements the Faker interface for faker. +func (f *faker) IP(options ...IPOption) string { opts := ipOptions{} for _, opt := range options { opt.apply(&opts) @@ -78,20 +75,20 @@ func IP(options ...IPOption) string { switch { case opts.v4 == opts.v6: sizes := []int{net.IPv4len, net.IPv6len} - size = sizes[rand.IntN(len(sizes))] + size = sizes[f.IntN(len(sizes))] case opts.v4: size = net.IPv4len case opts.v6: size = net.IPv6len } ip := make([]byte, size) - _, _ = crand.Read(ip) + _, _ = f.Read(ip) return net.IP(ip).String() } size = len(opts.network.Mask) raw := make([]byte, size) - _, _ = crand.Read(raw) + _, _ = f.Read(raw) ip := opts.network.IP for i, v := range raw { ip[i] += v &^ opts.network.Mask[i] @@ -147,9 +144,9 @@ func WithPortDynamic() PortOption { }) } -// Port generates a random port number between 1 and 65535 or in the range +// Port implements the Faker interface for faker. // specified by the given option. -func Port(options ...PortOption) uint32 { +func (f *faker) Port(options ...PortOption) uint32 { opts := portOptions{ min: 1, max: 65_535, @@ -157,5 +154,5 @@ func Port(options ...PortOption) uint32 { for _, opt := range options { opt.apply(&opts) } - return uint32(rand.IntN(opts.max+1-opts.min) + opts.min) //nolint:gosec + return uint32(f.IntN(opts.max+1-opts.min) + opts.min) //nolint:gosec } From 62cdcc3abb79babb9f52811b31d3273e70b78016 Mon Sep 17 00:00:00 2001 From: Alexandre Perrin Date: Tue, 24 Mar 2026 13:20:27 +0100 Subject: [PATCH 3/4] flow: provide a new struct API Introduce a flowfaker struct with its own PRNG instance, constructors, and struct methods matching the existing package API. Package-level functions are preserved in legacy.go for backwards compatibility, delegating to a shared package-level flowfaker instance. Signed-off-by: Alexandre Perrin --- flow/auth.go | 8 +- flow/drop.go | 16 ++-- flow/endpoint.go | 29 +++---- flow/event_type.go | 10 +-- flow/fake.go | 84 +++++++++++++++++++ flow/fake_test.go | 7 +- flow/flow.go | 58 +++++++------ flow/icmp.go | 50 ++++++----- flow/ip.go | 17 ++-- flow/is_reply.go | 9 +- flow/legacy.go | 144 ++++++++++++++++++++++++++++++++ flow/policy.go | 17 ++-- flow/protocol.go | 20 ++--- flow/service.go | 10 +-- flow/trace_observation_point.go | 25 +----- flow/trace_reason.go | 25 ++++++ flow/tracing.go | 15 ++-- flow/traffic.go | 6 +- flow/verdict.go | 10 +-- 19 files changed, 383 insertions(+), 177 deletions(-) create mode 100644 flow/fake.go create mode 100644 flow/legacy.go create mode 100644 flow/trace_reason.go diff --git a/flow/auth.go b/flow/auth.go index 1016395..bbb2358 100644 --- a/flow/auth.go +++ b/flow/auth.go @@ -4,12 +4,10 @@ package flow import ( - "math/rand/v2" - flowpb "github.com/cilium/cilium/api/v1/flow" ) -// AuthType generates a random AuthType. -func AuthType() flowpb.AuthType { - return flowpb.AuthType(rand.IntN(len(flowpb.AuthType_name))) //nolint:gosec +// AuthType implements FlowFaker for flowfaker. +func (f *flowfaker) AuthType() flowpb.AuthType { + return flowpb.AuthType(f.IntN(len(flowpb.AuthType_name))) //nolint:gosec } diff --git a/flow/drop.go b/flow/drop.go index cea3d17..a433e14 100644 --- a/flow/drop.go +++ b/flow/drop.go @@ -4,8 +4,6 @@ package flow import ( - "math/rand/v2" - flowpb "github.com/cilium/cilium/api/v1/flow" ) @@ -50,9 +48,12 @@ func WithDropReasonSubSet(dropReasons []flowpb.DropReason) DropReasonOption { }) } -// DropReason generates a DropReason. Options may be provided to customize the -// drop reasons to return. -func DropReason(options ...DropReasonOption) flowpb.DropReason { +// DropReason implements FlowFaker for flowfaker. +func (f *flowfaker) DropReason(options ...DropReasonOption) flowpb.DropReason { + // FIXME: evaluating all the options here for a single drop reason returned + // feels like we're paying a ton of overhead as soon as an option is given. + // Maybe consider moving this to the fake constructor, with maybe override + // possible on generation for special cases only? opts := dropReasonOptions{ nonDropProbability: 0.999, } @@ -60,10 +61,11 @@ func DropReason(options ...DropReasonOption) flowpb.DropReason { opt.apply(&opts) } - if f := rand.Float64(); f < opts.nonDropProbability { + if r := f.Float64(); r < opts.nonDropProbability { return flowpb.DropReason_DROP_REASON_UNKNOWN } + // FIXME: extract the static default set to be computed only once. if opts.set == nil { opts.set = make([]flowpb.DropReason, 0, len(flowpb.DropReason_name)-1) for k := range flowpb.DropReason_name { @@ -72,5 +74,5 @@ func DropReason(options ...DropReasonOption) flowpb.DropReason { } } } - return opts.set[rand.IntN(len(opts.set))] + return opts.set[f.IntN(len(opts.set))] } diff --git a/flow/endpoint.go b/flow/endpoint.go index bf6d94a..77ccda1 100644 --- a/flow/endpoint.go +++ b/flow/endpoint.go @@ -4,10 +4,7 @@ package flow import ( - "math/rand/v2" - flowpb "github.com/cilium/cilium/api/v1/flow" - "github.com/cilium/fake" ) type endpointOptions struct { @@ -71,20 +68,20 @@ func WithEndpointWorkloads(workloads map[string]string) EndpointOption { // Endpoint generates a random Endpoint. Options may be provided to customize // the endpoint to return. -func Endpoint(options ...EndpointOption) *flowpb.Endpoint { +func (f *flowfaker) Endpoint(options ...EndpointOption) *flowpb.Endpoint { opts := endpointOptions{ - namespace: fake.K8sNamespace(), - podName: fake.K8sPodName(), - labels: fake.K8sLabels(), - workloads: fakeWorkloads(), + namespace: f.K8sNamespace(), + podName: f.K8sPodName(), + labels: f.K8sLabels(), + workloads: f.fakeWorkloads(), } for _, opt := range options { opt.apply(&opts) } return &flowpb.Endpoint{ - ID: rand.Uint32(), - Identity: rand.Uint32(), + ID: f.Uint32(), + Identity: f.Uint32(), ClusterName: opts.cluster, Namespace: opts.namespace, Labels: opts.labels, @@ -105,15 +102,15 @@ var workloadKinds []string = []string{ "StatefulSet", } -func fakeWorkloads() map[string]string { +func (f *flowfaker) fakeWorkloads() map[string]string { workloads := map[string]string{ - fake.App(): workloadKinds[rand.IntN(len(workloadKinds))], + f.App(): workloadKinds[f.IntN(len(workloadKinds))], } - if rand.IntN(10) == 0 { // 10% chance of having more than one workload. - workloads[fake.App()] = workloadKinds[rand.IntN(len(workloadKinds))] + if f.IntN(10) == 0 { // 10% chance of having more than one workload. + workloads[f.App()] = workloadKinds[f.IntN(len(workloadKinds))] } - if rand.IntN(100) == 0 { // 1% chance of having more than two workloads. - workloads[fake.App()] = workloadKinds[rand.IntN(len(workloadKinds))] + if f.IntN(100) == 0 { // 1% chance of having more than two workloads. + workloads[f.App()] = workloadKinds[f.IntN(len(workloadKinds))] } return workloads } diff --git a/flow/event_type.go b/flow/event_type.go index 9edde59..1f0a70b 100644 --- a/flow/event_type.go +++ b/flow/event_type.go @@ -4,8 +4,6 @@ package flow import ( - "math/rand/v2" - flowpb "github.com/cilium/cilium/api/v1/flow" "github.com/cilium/cilium/pkg/monitor/api" ) @@ -21,15 +19,15 @@ var allEventTypes = []int32{ api.MessageTypeAgent, } -// EventType generates a random EventType. -func EventType() *flowpb.CiliumEventType { - typ := allEventTypes[rand.IntN(len(allEventTypes))] +// EventType implements FlowFaker for flowfaker. +func (f *flowfaker) EventType() *flowpb.CiliumEventType { + typ := allEventTypes[f.IntN(len(allEventTypes))] if typ == api.MessageTypeUnspec { return nil } return &flowpb.CiliumEventType{ Type: typ, // NOTE: AgentNotify* are the most numerous. - SubType: int32(rand.IntN(13)), //nolint:gosec + SubType: int32(f.IntN(13)), //nolint:gosec } } diff --git a/flow/fake.go b/flow/fake.go new file mode 100644 index 0000000..fb68e96 --- /dev/null +++ b/flow/fake.go @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of Cilium + +package flow + +import ( + "encoding/binary" + "io" + randv2 "math/rand/v2" + + "github.com/cilium/fake" + "google.golang.org/protobuf/types/known/wrapperspb" + + flowpb "github.com/cilium/cilium/api/v1/flow" +) + +// FlowFaker is the main interface exposed to generate fake flow data. +type FlowFaker interface { //nolint:interfacebloat + // AuthType generates a random AuthType. + AuthType() flowpb.AuthType + // DropReason generates a DropReason. + DropReason(options ...DropReasonOption) flowpb.DropReason + // Endpoint generates a random Endpoint. + Endpoint(options ...EndpointOption) *flowpb.Endpoint + // EventType generates a random EventType. + EventType() *flowpb.CiliumEventType + // New generates a random Hubble Flow. + NewFlow(options ...Option) *flowpb.Flow + // ICMPv4 generates a random flow ICMPv4 struct. + ICMPv4() *flowpb.ICMPv4 + // ICMPv6 generates a random flow ICMPv6 struct. + ICMPv6() *flowpb.ICMPv6 + // IP generates a random flow IP struct. + IP(options ...IPOption) *flowpb.IP + // IsReply returns either nil, or a wrapped boolean value reprenting true, + // or a wrapped boolean value reprenting false, with equal probability. + IsReply() *wrapperspb.BoolValue + // Policies generates a list of random policy references. + Policies() []*flowpb.Policy + // Layer4 generates a layer 4. If no option is provided, it will be TCP. + Layer4(options ...Layer4Option) *flowpb.Layer4 + // Service generates a random Service. + Service(options ...ServiceOption) *flowpb.Service + // TraceObservationPoint generates a random TraceObservationPoint. + TraceObservationPoint() flowpb.TraceObservationPoint + // TraceReason generates a random TraceReason. + TraceReason() flowpb.TraceReason + // TraceContext generates a TraceContext. + TraceContext(options ...TraceContextOption) *flowpb.TraceContext + // TrafficDirection generates a random TrafficDirection. + TrafficDirection() flowpb.TrafficDirection + // Verdict generates a FORWARDED or DROPPPED verdict randomly. + Verdict(options ...VerdictOption) flowpb.Verdict +} + +// NewFaker creates a new Faker using a random seed. +func NewFaker() FlowFaker { + // NOTE: We seed from the global math/rand/v2 source rather than + // crypto/rand to avoid errors handling; the faker should not be used for + // security-sensitive purposes. We use ChaCha8 rather than PCG as ChaCha8 + // implements io.Reader. + var seed [32]byte + for i := range 4 { + binary.LittleEndian.PutUint64(seed[i*8:], randv2.Uint64()) + } + return NewWithSource(randv2.NewChaCha8(seed)) +} + +// NewWithSource creates a new Faker using the given random source. Useful to +// control the faker output, e.g. for testing. +func NewWithSource(src fake.RandSourceReader) FlowFaker { + return &flowfaker{ + Faker: fake.NewWithSource(src), + Rand: randv2.New(src), + Reader: src, + } +} + +// faker is a private struct implementing Faker. +type flowfaker struct { + fake.Faker + *randv2.Rand + io.Reader +} diff --git a/flow/fake_test.go b/flow/fake_test.go index 36f8096..33dbf8c 100644 --- a/flow/fake_test.go +++ b/flow/fake_test.go @@ -8,7 +8,7 @@ import ( "sync" "testing" - fakeflow "github.com/cilium/fake/flow" + "github.com/cilium/fake/flow" ) func BenchmarkFakeFlow(b *testing.B) { @@ -21,8 +21,9 @@ func BenchmarkFakeFlow(b *testing.B) { wg.Add(p) for range p { go func() { - for _ = range n { - _ = fakeflow.New() + f := flow.NewFaker() + for range n { + _ = f.NewFlow() } wg.Done() }() diff --git a/flow/flow.go b/flow/flow.go index 31a9b7f..9b40abd 100644 --- a/flow/flow.go +++ b/flow/flow.go @@ -4,12 +4,10 @@ package flow import ( - "math/rand/v2" "time" flowpb "github.com/cilium/cilium/api/v1/flow" "github.com/cilium/cilium/pkg/monitor/api" - "github.com/cilium/fake" "github.com/google/uuid" "google.golang.org/protobuf/types/known/timestamppb" ) @@ -182,21 +180,21 @@ func WithFlowSourceNATProbability(probability float64) Option { } // New generates a random flow. Options may be provided to customize the flow. -func New(options ...Option) *flowpb.Flow { +func (f *flowfaker) NewFlow(options ...Option) *flowpb.Flow { opts := flowOptions{ time: time.Now().UTC(), - verdict: Verdict(), - authType: AuthType(), + verdict: f.Verdict(), + authType: f.AuthType(), typ: flowpb.FlowType_L3_L4, - nodeName: fake.K8sNodeName(), - nodeLabels: fake.K8sLabels(), + nodeName: f.K8sNodeName(), + nodeLabels: f.K8sLabels(), clusterName: "default", - sourceNames: fake.Names(5), - destNames: fake.Names(5), - epSource: Endpoint(), - epDest: Endpoint(), + sourceNames: f.Names(5), + destNames: f.Names(5), + epSource: f.Endpoint(), + epDest: f.Endpoint(), traceContextProbability: 0.1, - verdictByPolicies: Policies(), + verdictByPolicies: f.Policies(), sourceNATProbability: 0.1, } for _, opt := range options { @@ -212,11 +210,11 @@ func New(options ...Option) *flowpb.Flow { if opts.typ == flowpb.FlowType_L3_L4 { if opts.ip == nil { - opts.ip = IP(WithSourceNATProbability(opts.sourceNATProbability)) + opts.ip = f.IP(WithSourceNATProbability(opts.sourceNATProbability)) } if opts.l4 == nil { var l4Option Layer4Option - switch rand.IntN(5) { + switch f.IntN(5) { case 0: l4Option = WithLayer4TCP() case 1: @@ -228,15 +226,15 @@ func New(options ...Option) *flowpb.Flow { case 4: l4Option = WithLayer4ICMPv6() } - opts.l4 = Layer4(l4Option) + opts.l4 = f.Layer4(l4Option) } } // If an IP is defined, the Ethernet part shall be as well if opts.ip != nil && opts.ethernet == nil { opts.ethernet = &flowpb.Ethernet{ - Source: fake.MAC(), - Destination: fake.MAC(), + Source: f.MAC(), + Destination: f.MAC(), } } @@ -252,8 +250,8 @@ func New(options ...Option) *flowpb.Flow { // given probability. However, as the library doesn't support L7 flows yet, // add trace context unconditionally. var tc *flowpb.TraceContext - if p := rand.Float64(); p < opts.traceContextProbability { - tc = TraceContext() + if p := f.Float64(); p < opts.traceContextProbability { + tc = f.TraceContext() } flow := &flowpb.Flow{ @@ -274,19 +272,19 @@ func New(options ...Option) *flowpb.Flow { DestinationNames: opts.destNames, // TODO: L7 // NOTE: don't populate Reply as it is deprecated. - EventType: EventType(), - SourceService: Service(), - DestinationService: Service(), - TrafficDirection: TrafficDirection(), - PolicyMatchType: uint32(rand.IntN(5)), //nolint:gosec - TraceObservationPoint: TraceObservationPoint(), - TraceReason: TraceReason(), + EventType: f.EventType(), + SourceService: f.Service(), + DestinationService: f.Service(), + TrafficDirection: f.TrafficDirection(), + PolicyMatchType: uint32(f.IntN(5)), //nolint:gosec + TraceObservationPoint: f.TraceObservationPoint(), + TraceReason: f.TraceReason(), DropReasonDesc: opts.dropReason, - IsReply: IsReply(), + IsReply: f.IsReply(), TraceContext: tc, - SockXlatePoint: flowpb.SocketTranslationPoint(rand.IntN(len(flowpb.SocketTranslationPoint_name))), //nolint:gosec - SocketCookie: rand.Uint64(), - CgroupId: rand.Uint64(), + SockXlatePoint: flowpb.SocketTranslationPoint(f.IntN(len(flowpb.SocketTranslationPoint_name))), //nolint:gosec + SocketCookie: f.Uint64(), + CgroupId: f.Uint64(), // NOTE: don't populate Summary as it is deprecated. } diff --git a/flow/icmp.go b/flow/icmp.go index d5e12a9..6e28a47 100644 --- a/flow/icmp.go +++ b/flow/icmp.go @@ -4,8 +4,6 @@ package flow import ( - "math/rand/v2" - flowpb "github.com/cilium/cilium/api/v1/flow" "golang.org/x/net/ipv4" "golang.org/x/net/ipv6" @@ -28,9 +26,9 @@ var icmpv4Types = []ipv4.ICMPType{ ipv4.ICMPTypeExtendedEchoReply, // Extended Echo Reply } -// ICMPv4 generates a random ICMPv4 flow. -func ICMPv4() *flowpb.ICMPv4 { - t := icmpv4Types[rand.IntN(len(icmpv4Types))] +// ICMPv4 implements FlowFaker for flowfaker. +func (f *flowfaker) ICMPv4() *flowpb.ICMPv4 { + t := icmpv4Types[f.IntN(len(icmpv4Types))] // See https://www.iana.org/assignments/icmp-parameters/icmp-parameters.xhtml // https://tools.ietf.org/html/rfc792 @@ -40,27 +38,27 @@ func ICMPv4() *flowpb.ICMPv4 { switch t { case ipv4.ICMPTypeEchoReply: case ipv4.ICMPTypeDestinationUnreachable: - c = rand.IntN(16) + c = f.IntN(16) case ipv4.ICMPTypeRedirect: - c = rand.IntN(4) + c = f.IntN(4) case ipv4.ICMPTypeEcho: case ipv4.ICMPTypeRouterAdvertisement: - if rand.IntN(2) == 0 { // 1 in 2 chances + if f.IntN(2) == 0 { // 1 in 2 chances c = 16 } case ipv4.ICMPTypeRouterSolicitation: case ipv4.ICMPTypeTimeExceeded: - c = rand.IntN(2) + c = f.IntN(2) case ipv4.ICMPTypeParameterProblem: - c = rand.IntN(3) + c = f.IntN(3) case ipv4.ICMPTypeTimestamp: case ipv4.ICMPTypeTimestampReply: case ipv4.ICMPTypePhoturis: - c = rand.IntN(6) + c = f.IntN(6) case ipv4.ICMPTypeExtendedEchoRequest: - c = rand.IntN(5) + c = f.IntN(5) case ipv4.ICMPTypeExtendedEchoReply: - c = rand.IntN(5) + c = f.IntN(5) } return &flowpb.ICMPv4{ @@ -110,9 +108,9 @@ var icmpv6Types = []ipv6.ICMPType{ ipv6.ICMPTypeExtendedEchoReply, } -// ICMPv6 generates a random ICMPv6 flow. -func ICMPv6() *flowpb.ICMPv6 { - t := icmpv6Types[rand.IntN(len(icmpv6Types))] +// ICMPv6 implements FlowFaker for flowfaker. +func (f *flowfaker) ICMPv6() *flowpb.ICMPv6 { + t := icmpv6Types[f.IntN(len(icmpv6Types))] // See https://www.iana.org/assignments/icmpv6-parameters/icmpv6-parameters.xhtml // https://tools.ietf.org/html/rfc4443 @@ -120,12 +118,12 @@ func ICMPv6() *flowpb.ICMPv6 { var c int switch t { case ipv6.ICMPTypeDestinationUnreachable: - c = rand.IntN(16) + c = f.IntN(16) case ipv6.ICMPTypePacketTooBig: case ipv6.ICMPTypeTimeExceeded: - c = rand.IntN(2) + c = f.IntN(2) case ipv6.ICMPTypeParameterProblem: - c = rand.IntN(3) + c = f.IntN(3) case ipv6.ICMPTypeEchoRequest: case ipv6.ICMPTypeEchoReply: case ipv6.ICMPTypeMulticastListenerQuery: @@ -136,17 +134,17 @@ func ICMPv6() *flowpb.ICMPv6 { case ipv6.ICMPTypeNeighborSolicitation: case ipv6.ICMPTypeNeighborAdvertisement: case ipv6.ICMPTypeRedirect: - c = rand.IntN(4) + c = f.IntN(4) case ipv6.ICMPTypeRouterRenumbering: - if rand.IntN(3) == 2 { // 1 in 3 chance + if f.IntN(3) == 2 { // 1 in 3 chance c = 255 } else { - c = rand.IntN(2) + c = f.IntN(2) } case ipv6.ICMPTypeNodeInformationQuery: - c = rand.IntN(3) + c = f.IntN(3) case ipv6.ICMPTypeNodeInformationResponse: - c = rand.IntN(3) + c = f.IntN(3) case ipv6.ICMPTypeInverseNeighborDiscoverySolicitation: case ipv6.ICMPTypeInverseNeighborDiscoveryAdvertisement: case ipv6.ICMPTypeVersion2MulticastListenerReport: @@ -160,7 +158,7 @@ func ICMPv6() *flowpb.ICMPv6 { case ipv6.ICMPTypeMulticastRouterSolicitation: case ipv6.ICMPTypeMulticastRouterTermination: case ipv6.ICMPTypeFMIPv6: - c = rand.IntN(6) + c = f.IntN(6) case ipv6.ICMPTypeRPLControl: case ipv6.ICMPTypeILNPv6LocatorUpdate: case ipv6.ICMPTypeDuplicateAddressRequest: @@ -168,7 +166,7 @@ func ICMPv6() *flowpb.ICMPv6 { case ipv6.ICMPTypeMPLControl: case ipv6.ICMPTypeExtendedEchoRequest: case ipv6.ICMPTypeExtendedEchoReply: - c = rand.IntN(5) + c = f.IntN(5) } return &flowpb.ICMPv6{ diff --git a/flow/ip.go b/flow/ip.go index 70953d6..af961a0 100644 --- a/flow/ip.go +++ b/flow/ip.go @@ -4,8 +4,6 @@ package flow import ( - "math/rand/v2" - flowpb "github.com/cilium/cilium/api/v1/flow" "github.com/cilium/fake" ) @@ -40,22 +38,23 @@ func WithSourceNATProbability(probability float64) IPOption { }) } -func IP(options ...IPOption) *flowpb.IP { +// IP implements FlowFaker for flowfaker. +func (f *flowfaker) IP(options ...IPOption) *flowpb.IP { opts := ipOptions{} for _, opt := range options { opt.apply(&opts) } - if p := rand.Float64(); p < opts.sourceNATProbability { + if p := f.Float64(); p < opts.sourceNATProbability { return &flowpb.IP{ - Source: fake.IP(fake.WithIPCIDR("10.0.0.0/8")), - SourceXlated: fake.IP(fake.WithIPCIDR("172.16.0.0/12")), - Destination: fake.IP(fake.WithIPCIDR("172.16.0.0/12")), + Source: f.Faker.IP(fake.WithIPCIDR("10.0.0.0/8")), + SourceXlated: f.Faker.IP(fake.WithIPCIDR("172.16.0.0/12")), + Destination: f.Faker.IP(fake.WithIPCIDR("172.16.0.0/12")), IpVersion: flowpb.IPVersion_IPv4, } } else { return &flowpb.IP{ - Source: fake.IP(fake.WithIPCIDR("10.0.0.0/8")), - Destination: fake.IP(fake.WithIPCIDR("10.0.0.0/8")), + Source: f.Faker.IP(fake.WithIPCIDR("10.0.0.0/8")), + Destination: f.Faker.IP(fake.WithIPCIDR("10.0.0.0/8")), IpVersion: flowpb.IPVersion_IPv4, } } diff --git a/flow/is_reply.go b/flow/is_reply.go index 1dc3b08..8e07d33 100644 --- a/flow/is_reply.go +++ b/flow/is_reply.go @@ -4,15 +4,12 @@ package flow import ( - "math/rand/v2" - "google.golang.org/protobuf/types/known/wrapperspb" ) -// IsReply returns either nil, or a wrapped boolean value reprenting true, or a -// wrapped boolean value reprenting false, with equal probability. -func IsReply() *wrapperspb.BoolValue { - switch rand.IntN(3) { +// IsReply implements FlowFaker for flowfaker. +func (f *flowfaker) IsReply() *wrapperspb.BoolValue { + switch f.IntN(3) { case 0: return &wrapperspb.BoolValue{Value: false} case 1: diff --git a/flow/legacy.go b/flow/legacy.go new file mode 100644 index 0000000..a602d38 --- /dev/null +++ b/flow/legacy.go @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of Cilium + +package flow + +import ( + "sync" + + flowpb "github.com/cilium/cilium/api/v1/flow" + "google.golang.org/protobuf/types/known/wrapperspb" +) + +// globalFlowFaker is used by the legacy API of package level functions. +var ( + globalFlowFaker = NewFaker() + globalMu sync.Mutex +) + +// AuthType generates a random AuthType. +func AuthType() flowpb.AuthType { + globalMu.Lock() + defer globalMu.Unlock() + return globalFlowFaker.AuthType() +} + +// DropReason generates a DropReason. Options may be provided to customize the +// drop reasons to return. +func DropReason(options ...DropReasonOption) flowpb.DropReason { + globalMu.Lock() + defer globalMu.Unlock() + return globalFlowFaker.DropReason(options...) +} + +// Endpoint generates a random Endpoint. Options may be provided to customize +// the endpoint to return. +func Endpoint(options ...EndpointOption) *flowpb.Endpoint { + globalMu.Lock() + defer globalMu.Unlock() + return globalFlowFaker.Endpoint(options...) +} + +// EventType generates a random EventType. +func EventType() *flowpb.CiliumEventType { + globalMu.Lock() + defer globalMu.Unlock() + return globalFlowFaker.EventType() +} + +// New generates a random Hubble Flow. Options may be provided to customize the +// flow. +func New(options ...Option) *flowpb.Flow { + globalMu.Lock() + defer globalMu.Unlock() + return globalFlowFaker.NewFlow(options...) +} + +// ICMPv4 generates a random flow ICMPv4 struct. +func ICMPv4() *flowpb.ICMPv4 { + globalMu.Lock() + defer globalMu.Unlock() + return globalFlowFaker.ICMPv4() +} + +// ICMPv6 generates a random flow ICMPv6 struct. +func ICMPv6() *flowpb.ICMPv6 { + globalMu.Lock() + defer globalMu.Unlock() + return globalFlowFaker.ICMPv6() +} + +// IP generates a random flow IP struct. +func IP(options ...IPOption) *flowpb.IP { + globalMu.Lock() + defer globalMu.Unlock() + return globalFlowFaker.IP(options...) +} + +// IsReply returns either nil, or a wrapped boolean value reprenting true, or a +// wrapped boolean value reprenting false, with equal probability. +func IsReply() *wrapperspb.BoolValue { + globalMu.Lock() + defer globalMu.Unlock() + return globalFlowFaker.IsReply() +} + +// Policies generates a list of random policy references. +func Policies() []*flowpb.Policy { + globalMu.Lock() + defer globalMu.Unlock() + return globalFlowFaker.Policies() +} + +// Layer4 generates a layer 4. If no option is provided, it will be TCP. +func Layer4(options ...Layer4Option) *flowpb.Layer4 { + globalMu.Lock() + defer globalMu.Unlock() + return globalFlowFaker.Layer4(options...) +} + +// Service generates a random Service. Options may be provided to customize the +// service to return. +func Service(options ...ServiceOption) *flowpb.Service { + globalMu.Lock() + defer globalMu.Unlock() + return globalFlowFaker.Service(options...) +} + +// TraceObservationPoint generates a random TraceObservationPoint. +func TraceObservationPoint() flowpb.TraceObservationPoint { + globalMu.Lock() + defer globalMu.Unlock() + return globalFlowFaker.TraceObservationPoint() +} + +// TraceReason generates a random TraceReason. +func TraceReason() flowpb.TraceReason { + globalMu.Lock() + defer globalMu.Unlock() + return globalFlowFaker.TraceReason() +} + +// TraceContext generates a TraceContext. Options may be provided to customize +// the returned object. +func TraceContext(options ...TraceContextOption) *flowpb.TraceContext { + globalMu.Lock() + defer globalMu.Unlock() + return globalFlowFaker.TraceContext(options...) +} + +// TrafficDirection generates a random TrafficDirection. +func TrafficDirection() flowpb.TrafficDirection { + globalMu.Lock() + defer globalMu.Unlock() + return globalFlowFaker.TrafficDirection() +} + +// Verdict generates a FORWARDED or DROPPPED verdict randomly. The probability +// of the verdict being FORWARDED can be set using +// WithVerdictForwardedProbability. +func Verdict(options ...VerdictOption) flowpb.Verdict { + globalMu.Lock() + defer globalMu.Unlock() + return globalFlowFaker.Verdict(options...) +} diff --git a/flow/policy.go b/flow/policy.go index a280120..1a11b7d 100644 --- a/flow/policy.go +++ b/flow/policy.go @@ -4,22 +4,19 @@ package flow import ( - "math/rand/v2" - flowpb "github.com/cilium/cilium/api/v1/flow" - "github.com/cilium/fake" ) -// Policies generates a list of random policy references. -func Policies() []*flowpb.Policy { - n := rand.IntN(4) +// Policies implements FlowFaker for flowfaker. +func (f *flowfaker) Policies() []*flowpb.Policy { + n := f.IntN(4) policies := make([]*flowpb.Policy, n) for i := range n { policies[i] = &flowpb.Policy{ - Name: fake.Name(), - Namespace: fake.K8sNamespace(), - Labels: fake.K8sLabels(), - Revision: rand.Uint64() % 100, + Name: f.Name(), + Namespace: f.K8sNamespace(), + Labels: f.K8sLabels(), + Revision: f.Uint64() % 100, } } return policies diff --git a/flow/protocol.go b/flow/protocol.go index ecb1818..0cf42df 100644 --- a/flow/protocol.go +++ b/flow/protocol.go @@ -4,8 +4,6 @@ package flow import ( - "math/rand/v2" - flowpb "github.com/cilium/cilium/api/v1/flow" "github.com/cilium/fake" ) @@ -147,12 +145,12 @@ func WithLayer4DestinationPort(port uint32) Layer4Option { }) } -// Layer4 generates a layer 4. If no option is provided, it will be TCP. -func Layer4(options ...Layer4Option) *flowpb.Layer4 { +// Layer4 implements FlowFaker for flowfaker. +func (f *flowfaker) Layer4(options ...Layer4Option) *flowpb.Layer4 { opts := layer4Options{ - tcpFlags: randTCPFlags(), - srcPort: fake.Port(fake.WithPortUser()), - dstPort: fake.Port(fake.WithPortUser()), + tcpFlags: f.randTCPFlags(), + srcPort: f.Port(fake.WithPortUser()), + dstPort: f.Port(fake.WithPortUser()), } for _, opt := range options { opt.apply(&opts) @@ -192,13 +190,13 @@ func Layer4(options ...Layer4Option) *flowpb.Layer4 { case opts.icmpv4: return &flowpb.Layer4{ Protocol: &flowpb.Layer4_ICMPv4{ - ICMPv4: ICMPv4(), + ICMPv4: f.ICMPv4(), }, } case opts.icmpv6: return &flowpb.Layer4{ Protocol: &flowpb.Layer4_ICMPv6{ - ICMPv6: ICMPv6(), + ICMPv6: f.ICMPv6(), }, } } @@ -215,6 +213,6 @@ var tcpFlagsPatterns = []*flowpb.TCPFlags{ {ACK: true, FIN: true}, } -func randTCPFlags() *flowpb.TCPFlags { - return tcpFlagsPatterns[rand.IntN(len(tcpFlagsPatterns))] +func (f *flowfaker) randTCPFlags() *flowpb.TCPFlags { + return tcpFlagsPatterns[f.IntN(len(tcpFlagsPatterns))] } diff --git a/flow/service.go b/flow/service.go index 714b2a1..80b05d9 100644 --- a/flow/service.go +++ b/flow/service.go @@ -5,7 +5,6 @@ package flow import ( flowpb "github.com/cilium/cilium/api/v1/flow" - "github.com/cilium/fake" ) type serviceOptions struct { @@ -38,12 +37,11 @@ func WithServiceName(name string) ServiceOption { }) } -// Service generates a random Service. Options may be provided to customize the -// service to return. -func Service(options ...ServiceOption) *flowpb.Service { +// Service implements FlowFaker for flowfaker. +func (f *flowfaker) Service(options ...ServiceOption) *flowpb.Service { opts := serviceOptions{ - namespace: fake.K8sNamespace(), - name: fake.Name(), + namespace: f.K8sNamespace(), + name: f.Name(), } for _, opt := range options { opt.apply(&opts) diff --git a/flow/trace_observation_point.go b/flow/trace_observation_point.go index d6d67b6..5cdd7e5 100644 --- a/flow/trace_observation_point.go +++ b/flow/trace_observation_point.go @@ -4,8 +4,6 @@ package flow import ( - "math/rand/v2" - flowpb "github.com/cilium/cilium/api/v1/flow" ) @@ -27,24 +25,7 @@ var allTraceObservationPoints = []flowpb.TraceObservationPoint{ flowpb.TraceObservationPoint_TO_CRYPTO, } -// TraceObservationPoint generates a random TraceObservationPoint. -func TraceObservationPoint() flowpb.TraceObservationPoint { - return allTraceObservationPoints[rand.IntN(len(allTraceObservationPoints))] -} - -var allTraceReasons = []flowpb.TraceReason{ - flowpb.TraceReason_TRACE_REASON_UNKNOWN, - flowpb.TraceReason_NEW, - flowpb.TraceReason_ESTABLISHED, - flowpb.TraceReason_REPLY, - flowpb.TraceReason_RELATED, - // flowpb.TraceReason_REOPENED, -- Deprecated - flowpb.TraceReason_SRV6_ENCAP, - flowpb.TraceReason_SRV6_DECAP, - // flowpb.TraceReason_ENCRYPT_OVERLAY, -- Deprecated -} - -// TraceReason generates a random TraceReason. -func TraceReason() flowpb.TraceReason { - return allTraceReasons[rand.IntN(len(allTraceReasons))] +// TraceObservationPoint implements FlowFaker for flowfaker. +func (f *flowfaker) TraceObservationPoint() flowpb.TraceObservationPoint { + return allTraceObservationPoints[f.IntN(len(allTraceObservationPoints))] } diff --git a/flow/trace_reason.go b/flow/trace_reason.go new file mode 100644 index 0000000..aea312b --- /dev/null +++ b/flow/trace_reason.go @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of Cilium + +package flow + +import ( + flowpb "github.com/cilium/cilium/api/v1/flow" +) + +var allTraceReasons = []flowpb.TraceReason{ + flowpb.TraceReason_TRACE_REASON_UNKNOWN, + flowpb.TraceReason_NEW, + flowpb.TraceReason_ESTABLISHED, + flowpb.TraceReason_REPLY, + flowpb.TraceReason_RELATED, + // flowpb.TraceReason_REOPENED, -- Deprecated + flowpb.TraceReason_SRV6_ENCAP, + flowpb.TraceReason_SRV6_DECAP, + // flowpb.TraceReason_ENCRYPT_OVERLAY, -- Deprecated +} + +// TraceReason implements FlowFaker for flowfaker. +func (f *flowfaker) TraceReason() flowpb.TraceReason { + return allTraceReasons[f.IntN(len(allTraceReasons))] +} diff --git a/flow/tracing.go b/flow/tracing.go index 7025f2d..b37c9a3 100644 --- a/flow/tracing.go +++ b/flow/tracing.go @@ -4,9 +4,7 @@ package flow import ( - crand "crypto/rand" "encoding/hex" - "math/rand/v2" flowpb "github.com/cilium/cilium/api/v1/flow" ) @@ -37,16 +35,15 @@ func WithTraceIDs(traceIDs []string) TraceContextOption { }) } -// TraceContext generates a TraceContext. Options may be provided to customize -// the returned object. -func TraceContext(options ...TraceContextOption) *flowpb.TraceContext { +// TraceContext implements FlowFaker for flowfaker. +func (f *flowfaker) TraceContext(options ...TraceContextOption) *flowpb.TraceContext { opts := traceContextOptions{} for _, opt := range options { opt.apply(&opts) } - traceID := fakeTraceID() + traceID := f.fakeTraceID() if len(opts.traceIDs) != 0 { - traceID = opts.traceIDs[rand.IntN(len(opts.traceIDs))] + traceID = opts.traceIDs[f.IntN(len(opts.traceIDs))] } return &flowpb.TraceContext{ Parent: &flowpb.TraceParent{ @@ -58,10 +55,10 @@ func TraceContext(options ...TraceContextOption) *flowpb.TraceContext { // fakeTraceID generates a fake trace ID. See the W3C Trace Context // specification for details: // https://www.w3.org/TR/trace-context/#trace-id -func fakeTraceID() string { +func (f *flowfaker) fakeTraceID() string { var tid [traceIDLen]byte for !isValidTraceID(tid[:]) { - _, _ = crand.Read(tid[:]) + _, _ = f.Read(tid[:]) } return hex.EncodeToString(tid[:]) } diff --git a/flow/traffic.go b/flow/traffic.go index e65b29b..c4fe1c2 100644 --- a/flow/traffic.go +++ b/flow/traffic.go @@ -4,12 +4,10 @@ package flow import ( - "math/rand/v2" - flowpb "github.com/cilium/cilium/api/v1/flow" ) // TrafficDirection generates a random TrafficDirection. -func TrafficDirection() flowpb.TrafficDirection { - return flowpb.TrafficDirection(rand.IntN(len(flowpb.TrafficDirection_name))) //nolint:gosec +func (f *flowfaker) TrafficDirection() flowpb.TrafficDirection { + return flowpb.TrafficDirection(f.IntN(len(flowpb.TrafficDirection_name))) //nolint:gosec } diff --git a/flow/verdict.go b/flow/verdict.go index e4f1a99..8b7c78a 100644 --- a/flow/verdict.go +++ b/flow/verdict.go @@ -4,8 +4,6 @@ package flow import ( - "math/rand/v2" - flowpb "github.com/cilium/cilium/api/v1/flow" ) @@ -39,10 +37,8 @@ func WithVerdictForwardedProbability(probability float64) VerdictOption { }) } -// Verdict generates a FORWARDED or DROPPPED verdict randomly. The probability -// of the verdict being FORWARDED can be set using -// WithVerdictForwardedProbability. -func Verdict(options ...VerdictOption) flowpb.Verdict { +// Verdict implements FlowFaker for flowfaker. +func (f *flowfaker) Verdict(options ...VerdictOption) flowpb.Verdict { opts := verdictOptions{ forwardProbability: 0.999, } @@ -50,7 +46,7 @@ func Verdict(options ...VerdictOption) flowpb.Verdict { opt.apply(&opts) } - if f := rand.Float64(); f < opts.forwardProbability { + if p := f.Float64(); p < opts.forwardProbability { return flowpb.Verdict_FORWARDED } //TODO: return other verdict types? With which probability? From 58fd3f66e92539e576b04b22877123a981f89b11 Mon Sep 17 00:00:00 2001 From: Alexandre Perrin Date: Tue, 24 Mar 2026 15:45:51 +0100 Subject: [PATCH 4/4] cmd: adopt the new struct API Use a Faker struct in each command handler and its methods rather than the (now) legacy package-level functions. Signed-off-by: Alexandre Perrin --- cmd/internal/cmd/flow/flow.go | 3 ++- cmd/internal/cmd/ip/ip.go | 3 ++- cmd/internal/cmd/mac/mac.go | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/cmd/internal/cmd/flow/flow.go b/cmd/internal/cmd/flow/flow.go index 641a86e..3da1ccb 100644 --- a/cmd/internal/cmd/flow/flow.go +++ b/cmd/internal/cmd/flow/flow.go @@ -133,13 +133,14 @@ func runFlows(p *printer.Printer) error { } } + gen := fakeflow.NewFaker() for i := range opts.count { // used to get a random node name idx := rand.IntN(len(nodesIPs)) // add a small amount of jitter to the timestamps so they don't have perfect time gaps jitter := rand.Int64N(int64(winRange)) t := since.Add(time.Duration(i)*winRange + time.Duration(jitter)) - flow := fakeflow.New( + flow := gen.NewFlow( fakeflow.WithFlowTime(t), fakeflow.WithFlowNodeName(nodesIPs[idx].name), fakeflow.WithFlowIP(nodesIPs[idx].ip), diff --git a/cmd/internal/cmd/ip/ip.go b/cmd/internal/cmd/ip/ip.go index d0b691a..ac2dfa4 100644 --- a/cmd/internal/cmd/ip/ip.go +++ b/cmd/internal/cmd/ip/ip.go @@ -55,8 +55,9 @@ func runIPs(cmd *cobra.Command) error { ipOptions = append(ipOptions, fake.WithIPCIDR(opts.cidr)) } + gen := fake.New() for range opts.count { - fmt.Fprintln(cmd.OutOrStdout(), fake.IP(ipOptions...)) + fmt.Fprintln(cmd.OutOrStdout(), gen.IP(ipOptions...)) } return nil } diff --git a/cmd/internal/cmd/mac/mac.go b/cmd/internal/cmd/mac/mac.go index 0666ad0..bdc7e56 100644 --- a/cmd/internal/cmd/mac/mac.go +++ b/cmd/internal/cmd/mac/mac.go @@ -34,8 +34,9 @@ func New() *cobra.Command { } func runMACs(cmd *cobra.Command) error { + gen := fake.New() for range opts.count { - fmt.Fprintln(cmd.OutOrStdout(), fake.MAC()) + fmt.Fprintln(cmd.OutOrStdout(), gen.MAC()) } return nil }