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 } 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/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 new file mode 100644 index 0000000..33dbf8c --- /dev/null +++ b/flow/fake_test.go @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of Cilium + +package flow_test + +import ( + "runtime" + "sync" + "testing" + + "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() { + f := flow.NewFaker() + for range n { + _ = f.NewFlow() + } + wg.Done() + }() + } + wg.Wait() +} 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? 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 }