From 4e9effcf4cfec98e6ca56558186efcb4095877e6 Mon Sep 17 00:00:00 2001 From: pascal Date: Mon, 20 Jul 2026 17:50:31 +0200 Subject: [PATCH] remove old math rand lib --- .../modules/agentnetwork/labelgen/labelgen.go | 12 +++--- .../agentnetwork/labelgen/labelgen_test.go | 25 ++++------- .../internals/modules/agentnetwork/manager.go | 11 +---- management/server/account.go | 6 +-- management/server/group_ipv6_test.go | 3 +- management/server/idp/util.go | 16 ++++---- management/server/types/network.go | 41 ++++++++----------- management/server/util/util.go | 15 +++++++ 8 files changed, 59 insertions(+), 70 deletions(-) diff --git a/management/internals/modules/agentnetwork/labelgen/labelgen.go b/management/internals/modules/agentnetwork/labelgen/labelgen.go index b45ff4ea8..54ec6845d 100644 --- a/management/internals/modules/agentnetwork/labelgen/labelgen.go +++ b/management/internals/modules/agentnetwork/labelgen/labelgen.go @@ -3,9 +3,10 @@ package labelgen import ( "fmt" - "math/rand" "sort" "sync" + + "github.com/netbirdio/netbird/management/server/util" ) // pickAttempts caps the random retries before falling back to the @@ -40,16 +41,15 @@ func uniqueWords() []string { // PickUnique selects a label not already in `taken`. It tries up to // pickAttempts random picks; on exhaustion it scans the deduplicated // wordlist for any remaining free entry, and if none is left appends -// `-` to a deterministic word and returns. The caller -// is responsible for seeding rng (math/rand). -func PickUnique(rng *rand.Rand, taken map[string]struct{}, fallbackSuffix string) string { +// `-` to a random word and returns. +func PickUnique(taken map[string]struct{}, fallbackSuffix string) string { pool := uniqueWords() if len(pool) == 0 { return fallbackSuffix } for i := 0; i < pickAttempts; i++ { - w := pool[rng.Intn(len(pool))] + w := pool[util.RandIntn(len(pool))] if _, ok := taken[w]; !ok { return w } @@ -61,6 +61,6 @@ func PickUnique(rng *rand.Rand, taken map[string]struct{}, fallbackSuffix string } } - w := pool[rng.Intn(len(pool))] + w := pool[util.RandIntn(len(pool))] return fmt.Sprintf("%s-%s", w, fallbackSuffix) } diff --git a/management/internals/modules/agentnetwork/labelgen/labelgen_test.go b/management/internals/modules/agentnetwork/labelgen/labelgen_test.go index f03a3501d..93c93b7f0 100644 --- a/management/internals/modules/agentnetwork/labelgen/labelgen_test.go +++ b/management/internals/modules/agentnetwork/labelgen/labelgen_test.go @@ -1,7 +1,7 @@ package labelgen import ( - "math/rand" + "slices" "strings" "testing" @@ -9,19 +9,12 @@ import ( "github.com/stretchr/testify/require" ) -// TestPickUnique_DeterministicWithSeededRng locks the property the -// caller relies on: same seed + same taken set → same pick. Without -// that, the bootstrap flow can't reproduce a label across retries. -func TestPickUnique_DeterministicWithSeededRng(t *testing.T) { - taken := map[string]struct{}{} +// TestPickUnique_ReturnsWordFromPool confirms a pick against an empty +// taken set is always drawn verbatim from the wordlist. +func TestPickUnique_ReturnsWordFromPool(t *testing.T) { + got := PickUnique(map[string]struct{}{}, "abcd") - rngA := rand.New(rand.NewSource(42)) - rngB := rand.New(rand.NewSource(42)) - - a := PickUnique(rngA, taken, "abcd") - b := PickUnique(rngB, taken, "abcd") - - assert.Equal(t, a, b, "Same seed and taken set must produce identical pick") + assert.True(t, slices.Contains(uniqueWords(), got), "Pick %q must be drawn from the wordlist", got) } // TestPickUnique_AvoidsTakenWordsWhenMostAreReserved seeds taken with @@ -46,8 +39,7 @@ func TestPickUnique_AvoidsTakenWordsWhenMostAreReserved(t *testing.T) { taken[w] = struct{}{} } - rng := rand.New(rand.NewSource(7)) - got := PickUnique(rng, taken, "abcd") + got := PickUnique(taken, "abcd") _, isFree := free[got] assert.True(t, isFree, "PickUnique must return one of the free words; got %q", got) @@ -65,8 +57,7 @@ func TestPickUnique_FallsBackWhenAllReserved(t *testing.T) { taken[w] = struct{}{} } - rng := rand.New(rand.NewSource(99)) - got := PickUnique(rng, taken, "abcd") + got := PickUnique(taken, "abcd") assert.True(t, strings.HasSuffix(got, "-abcd"), "Exhausted pool must produce -; got %q", got) diff --git a/management/internals/modules/agentnetwork/manager.go b/management/internals/modules/agentnetwork/manager.go index d88e0d77c..9b9c1d2c3 100644 --- a/management/internals/modules/agentnetwork/manager.go +++ b/management/internals/modules/agentnetwork/manager.go @@ -4,7 +4,6 @@ import ( "context" "errors" "fmt" - "math/rand" "slices" "strings" "sync" @@ -123,11 +122,6 @@ type managerImpl struct { // accountID, then by synthesised service ID. reconcileMu sync.Mutex reconcileCache map[string]map[string]*proto.ProxyMapping - - // labelRngMu guards labelRng. PickUnique consumes math/rand.Source - // state; concurrent provider creates would otherwise race. - labelRngMu sync.Mutex - labelRng *rand.Rand } // NewManager constructs the persistent Agent Network manager. The @@ -147,7 +141,6 @@ func NewManager( permissionsManager: permissionsManager, proxyController: proxyController, reconcileCache: make(map[string]map[string]*proto.ProxyMapping), - labelRng: rand.New(rand.NewSource(time.Now().UnixNano())), } } @@ -653,9 +646,7 @@ func (m *managerImpl) bootstrapSettingsIfNeeded(ctx context.Context, accountID, suffix = suffix[:4] } - m.labelRngMu.Lock() - subdomain := labelgen.PickUnique(m.labelRng, taken, suffix) - m.labelRngMu.Unlock() + subdomain := labelgen.PickUnique(taken, suffix) now := time.Now().UTC() settings := &types.Settings{ diff --git a/management/server/account.go b/management/server/account.go index 9d2759cb7..c5bb2806f 100644 --- a/management/server/account.go +++ b/management/server/account.go @@ -4,7 +4,6 @@ import ( "context" "errors" "fmt" - "math/rand" "net" "net/netip" "os" @@ -63,7 +62,7 @@ const ( type userLoggedInOnce bool func cacheEntryExpiration() time.Duration { - r := rand.Intn(int(nbcache.DefaultIDPCacheExpirationMax.Milliseconds()-nbcache.DefaultIDPCacheExpirationMin.Milliseconds())) + int(nbcache.DefaultIDPCacheExpirationMin.Milliseconds()) + r := util.RandIntn(int(nbcache.DefaultIDPCacheExpirationMax.Milliseconds()-nbcache.DefaultIDPCacheExpirationMin.Milliseconds())) + int(nbcache.DefaultIDPCacheExpirationMin.Milliseconds()) return time.Duration(r) * time.Millisecond } @@ -2455,8 +2454,7 @@ func (am *DefaultAccountManager) ensureIPv6Subnet(ctx context.Context, transacti return transaction.UpdateAccountNetworkV6(ctx, accountID, network.NetV6) } if network.NetV6.IP == nil { - r := rand.New(rand.NewSource(time.Now().UnixNano())) - network.NetV6 = types.AllocateIPv6Subnet(r) + network.NetV6 = types.AllocateIPv6Subnet() // Sync settings to match the allocated subnet so SaveAccountSettings persists it. ones, _ := network.NetV6.Mask.Size() diff --git a/management/server/group_ipv6_test.go b/management/server/group_ipv6_test.go index dfb436060..2679aa7c2 100644 --- a/management/server/group_ipv6_test.go +++ b/management/server/group_ipv6_test.go @@ -2,7 +2,6 @@ package server import ( "context" - "math/rand" "testing" "time" @@ -28,7 +27,7 @@ func TestGroupIPv6Assignment(t *testing.T) { require.NoError(t, err) // Allocate IPv6 subnet for the account - account.Network.NetV6 = types.AllocateIPv6Subnet(rand.New(rand.NewSource(time.Now().UnixNano()))) + account.Network.NetV6 = types.AllocateIPv6Subnet() require.NoError(t, am.Store.SaveAccount(ctx, account)) // Create setup key diff --git a/management/server/idp/util.go b/management/server/idp/util.go index ed82fb9e3..6545c2a69 100644 --- a/management/server/idp/util.go +++ b/management/server/idp/util.go @@ -2,11 +2,12 @@ package idp import ( "encoding/json" - "math/rand" "net/url" "os" "strings" "time" + + "github.com/netbirdio/netbird/management/server/util" ) var ( @@ -33,31 +34,32 @@ func GeneratePassword(passwordLength, minSpecialChar, minNum, minUpperCase int) //Set special character for i := 0; i < minSpecialChar; i++ { - random := rand.Intn(len(specialCharSet)) + random := util.RandIntn(len(specialCharSet)) password.WriteString(string(specialCharSet[random])) } //Set numeric for i := 0; i < minNum; i++ { - random := rand.Intn(len(numberSet)) + random := util.RandIntn(len(numberSet)) password.WriteString(string(numberSet[random])) } //Set uppercase for i := 0; i < minUpperCase; i++ { - random := rand.Intn(len(upperCharSet)) + random := util.RandIntn(len(upperCharSet)) password.WriteString(string(upperCharSet[random])) } remainingLength := passwordLength - minSpecialChar - minNum - minUpperCase for i := 0; i < remainingLength; i++ { - random := rand.Intn(len(allCharSet)) + random := util.RandIntn(len(allCharSet)) password.WriteString(string(allCharSet[random])) } inRune := []rune(password.String()) - rand.Shuffle(len(inRune), func(i, j int) { + for i := len(inRune) - 1; i > 0; i-- { + j := util.RandIntn(i + 1) inRune[i], inRune[j] = inRune[j], inRune[i] - }) + } return string(inRune) } diff --git a/management/server/types/network.go b/management/server/types/network.go index fe67bfd97..024a4a0fe 100644 --- a/management/server/types/network.go +++ b/management/server/types/network.go @@ -1,14 +1,13 @@ package types import ( + "crypto/rand" "encoding/binary" "fmt" - "math/rand" "net" "net/netip" "slices" "sync" - "time" "github.com/c-robinson/iplib" "github.com/rs/xid" @@ -137,14 +136,12 @@ func NewNetwork() *Network { n := iplib.NewNet4(net.ParseIP("100.64.0.0"), NetSize) sub, _ := n.Subnet(SubnetSize) - s := rand.NewSource(time.Now().UnixNano()) - r := rand.New(s) - intn := r.Intn(len(sub)) + intn := util.RandIntn(len(sub)) return &Network{ Identifier: xid.New().String(), Net: sub[intn].IPNet, - NetV6: AllocateIPv6Subnet(r), + NetV6: AllocateIPv6Subnet(), Dns: "", Serial: 0, } @@ -154,18 +151,13 @@ func NewNetwork() *Network { // The format follows RFC 4193 section 3.1: fd + 40-bit Global ID + 16-bit Subnet ID. // The Global ID and Subnet ID are randomized (simplified from the SHA-1 algorithm // in section 3.2.2), giving 2^56 possible /64 subnets across all accounts. -func AllocateIPv6Subnet(r *rand.Rand) net.IPNet { +func AllocateIPv6Subnet() net.IPNet { ip := make(net.IP, 16) ip[0] = 0xfd - // Bytes 1-5: 40-bit random Global ID - ip[1] = byte(r.Intn(256)) - ip[2] = byte(r.Intn(256)) - ip[3] = byte(r.Intn(256)) - ip[4] = byte(r.Intn(256)) - ip[5] = byte(r.Intn(256)) - // Bytes 6-7: 16-bit random Subnet ID - ip[6] = byte(r.Intn(256)) - ip[7] = byte(r.Intn(256)) + // Bytes 1-5: 40-bit random Global ID, bytes 6-7: 16-bit random Subnet ID + if _, err := rand.Read(ip[1:8]); err != nil { + panic(err) + } return net.IPNet{ IP: ip, @@ -217,11 +209,10 @@ func AllocatePeerIP(prefix netip.Prefix, takenIps []netip.Addr) (netip.Addr, err taken[binary.BigEndian.Uint32(ab[:])] = struct{}{} } - rng := rand.New(rand.NewSource(time.Now().UnixNano())) maxAttempts := (int(totalIPs) - len(taken)) / 100 for i := 0; i < maxAttempts; i++ { - offset := uint32(rng.Intn(int(totalIPs-2))) + 1 + offset := uint32(util.RandIntn(int(totalIPs-2))) + 1 candidate := baseIP + offset if _, exists := taken[candidate]; !exists { return uint32ToIP(candidate), nil @@ -245,8 +236,7 @@ func AllocateRandomPeerIP(prefix netip.Prefix) (netip.Addr, error) { hostBits := 32 - prefix.Bits() totalIPs := uint32(1 << hostBits) - rng := rand.New(rand.NewSource(time.Now().UnixNano())) - offset := uint32(rng.Intn(int(totalIPs-2))) + 1 + offset := uint32(util.RandIntn(int(totalIPs-2))) + 1 candidate := baseIP + offset return uint32ToIP(candidate), nil @@ -262,23 +252,26 @@ func AllocateRandomPeerIPv6(prefix netip.Prefix) (netip.Addr, error) { ip := prefix.Addr().As16() - rng := rand.New(rand.NewSource(time.Now().UnixNano())) - // Determine which byte the host bits start in firstHostByte := ones / 8 // If the prefix doesn't end on a byte boundary, handle the partial byte partialBits := ones % 8 + var rnd [16]byte + if _, err := rand.Read(rnd[firstHostByte:]); err != nil { + return netip.Addr{}, err + } + if partialBits > 0 { // Keep the network bits in the partial byte, randomize the rest hostMask := byte(0xff >> partialBits) - ip[firstHostByte] = (ip[firstHostByte] & ^hostMask) | (byte(rng.Intn(256)) & hostMask) + ip[firstHostByte] = (ip[firstHostByte] & ^hostMask) | (rnd[firstHostByte] & hostMask) firstHostByte++ } // Randomize remaining full host bytes for i := firstHostByte; i < 16; i++ { - ip[i] = byte(rng.Intn(256)) + ip[i] = rnd[i] } // Avoid all-zeros and all-ones host parts by checking only host bits. diff --git a/management/server/util/util.go b/management/server/util/util.go index 617484274..32770d7e3 100644 --- a/management/server/util/util.go +++ b/management/server/util/util.go @@ -1,5 +1,20 @@ package util +import ( + "crypto/rand" + "math/big" +) + +// RandIntn returns a uniformly distributed int in [0, n) sourced from +// crypto/rand. It panics if n <= 0 or the platform randomness source fails. +func RandIntn(n int) int { + v, err := rand.Int(rand.Reader, big.NewInt(int64(n))) + if err != nil { + panic(err) + } + return int(v.Int64()) +} + // Difference returns the elements in `a` that aren't in `b`. func Difference(a, b []string) []string { mb := make(map[string]struct{}, len(b))