Repository: scionproto/scion
Pull request #4692 (2025-01-30 14:49:56)

refactor: use math/rand/v2
- **refactor:** Use `math/rand/v2` instead of `math/rand` (better performance, better API like automatic seeding, etc.).
- **refactor:** Remove unused functions `scrypto.RandUint64` and `scrypto.RandInt64` and error sentinel values `ErrInvalidNonceSize` and `ErrUnableToGenerateNonce` from pkg/scrypto.
- **refactor:** Replace deprecated `common.ErrMsg` sentinel errors with `errors.New` in `pkg/scrypto`.
- **test:** Use `crypto/rand.Read` instead of deprecated `math/rand.Read` in dataplane test.

jiceatscion: 

<!-- Reviewable:start -->
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/scionproto/scion/4692)
<!-- Reviewable:end -->


Patch:
diff --git a/control/segreq/fetcher.go b/control/segreq/fetcher.go
index c90955723..c2859e15c 100644
--- a/control/segreq/fetcher.go
+++ b/control/segreq/fetcher.go
@@ -17,7 +17,7 @@ package segreq
 import (
 	"context"
 	"fmt"
-	"math/rand"
+	"math/rand/v2"
 	"net"
 	"time"
 
@@ -196,7 +196,7 @@ func (p *dstProvider) Dst(ctx context.Context, req segfetcher.Request) (net.Addr
 		if len(paths) == 0 {
 			return nil, segfetcher.ErrNotReachable
 		}
-		path = paths[rand.Intn(len(paths))]
+		path = paths[rand.IntN(len(paths))]
 	default:
 		panic(fmt.Errorf("Unsupported segment type for request forwarding. "+
 			"Up segment should have been resolved locally. SegType: %s", req.SegType))
diff --git a/control/segreq/helpers.go b/control/segreq/helpers.go
index fad893c0f..7692e144c 100644
--- a/control/segreq/helpers.go
+++ b/control/segreq/helpers.go
@@ -16,7 +16,7 @@ package segreq
 
 import (
 	"context"
-	"math/rand"
+	"math/rand/v2"
 
 	"github.com/scionproto/scion/pkg/addr"
 	"github.com/scionproto/scion/pkg/private/serrors"
@@ -71,7 +71,7 @@ func (s *SegSelector) SelectSeg(ctx context.Context,
 	if len(segs) < 1 {
 		return nil, serrors.New("no segments found")
 	}
-	seg := segs[rand.Intn(len(segs))]
+	seg := segs[rand.IntN(len(segs))]
 
 	svcaddr, err := s.Pather.GetPath(addr.SvcCS, seg)
 	// odd interface, builds address not path. Use GetPath to convert to snet.Path
diff --git a/pkg/daemon/apitypes.go b/pkg/daemon/apitypes.go
index 904a7fab4..c3c66a005 100644
--- a/pkg/daemon/apitypes.go
+++ b/pkg/daemon/apitypes.go
@@ -16,7 +16,7 @@ package daemon
 
 import (
 	"context"
-	"math/rand"
+	"math/rand/v2"
 	"net"
 
 	"github.com/scionproto/scion/pkg/addr"
@@ -77,7 +77,7 @@ func (h TopoQuerier) UnderlayAnycast(ctx context.Context, svc addr.SVC) (*net.UD
 	if !ok || len(entry) == 0 {
 		return nil, serrors.New("no entry found", "svc", svc, "services", r)
 	}
-	a, err := net.ResolveUDPAddr("udp", entry[rand.Intn(len(entry))])
+	a, err := net.ResolveUDPAddr("udp", entry[rand.IntN(len(entry))])
 	if err != nil {
 		return nil, err
 	}
diff --git a/pkg/log/debugid.go b/pkg/log/debugid.go
index a0f483951..bfa631aa7 100644
--- a/pkg/log/debugid.go
+++ b/pkg/log/debugid.go
@@ -16,7 +16,7 @@ package log
 
 import (
 	"fmt"
-	"math/rand"
+	"math/rand/v2"
 )
 
 // DebugID is used to correlate behavior in logs. A DebugID is allocated
diff --git a/pkg/scrypto/BUILD.bazel b/pkg/scrypto/BUILD.bazel
index df3e1a58b..1faaf71c1 100644
--- a/pkg/scrypto/BUILD.bazel
+++ b/pkg/scrypto/BUILD.bazel
@@ -5,13 +5,11 @@ go_library(
     srcs = [
         "mac.go",
         "pem.go",
-        "rand.go",
         "version.go",
     ],
     importpath = "github.com/scionproto/scion/pkg/scrypto",
     visibility = ["//visibility:public"],
     deps = [
-        "//pkg/private/common:go_default_library",
         "//pkg/private/serrors:go_default_library",
         "@com_github_dchest_cmac//:go_default_library",
         "@org_golang_x_crypto//pbkdf2:go_default_library",
diff --git a/pkg/scrypto/mac.go b/pkg/scrypto/mac.go
index 589ca5cb2..7f3b35784 100644
--- a/pkg/scrypto/mac.go
+++ b/pkg/scrypto/mac.go
@@ -17,24 +17,22 @@ package scrypto
 import (
 	"crypto/aes"
 	"crypto/sha256"
+	"errors"
 	"hash"
 
 	"github.com/dchest/cmac"
 	"golang.org/x/crypto/pbkdf2"
 
-	"github.com/scionproto/scion/pkg/private/common"
 	"github.com/scionproto/scion/pkg/private/serrors"
 )
 
-const (
-	ErrCipherFailure common.ErrMsg = "Unable to initialize AES cipher"
-	ErrMacFailure    common.ErrMsg = "Unable to initialize Mac"
-)
-
 var (
-	hfMacSalt = []byte("Derive OF Key")
+	ErrCipherFailure = errors.New("unable to initialize AES cipher")
+	ErrMacFailure    = errors.New("unable to initialize Mac")
 )
 
+var hfMacSalt = []byte("Derive OF Key")
+
 func InitMac(key []byte) (hash.Hash, error) {
 	block, err := aes.NewCipher(key)
 	if err != nil {
diff --git a/pkg/scrypto/rand.go b/pkg/scrypto/rand.go
deleted file mode 100644
index b998d73ab..000000000
--- a/pkg/scrypto/rand.go
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright 2018 ETH Zurich, Anapaya Systems
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package scrypto
-
-import (
-	"crypto/rand"
-	"encoding/binary"
-	mrand "math/rand"
-
-	"github.com/scionproto/scion/pkg/private/common"
-)
-
-const (
-	ErrInvalidNonceSize      common.ErrMsg = "Invalid nonce size"
-	ErrUnableToGenerateNonce common.ErrMsg = "Unable to generate nonce"
-)
-
-func init() {
-	// Seed math/rand's default generator with a random value, once.
-	mrand.Seed(RandInt64())
-}
-
-func RandUint64() uint64 {
-	b := make([]byte, 8)
-	if _, err := rand.Read(b); err != nil {
-		// If this happens, there's some serious problem with the runtime or
-		// OS, and there's nothing we can do about it.
-		panic("No random numbers available")
-	}
-	return binary.LittleEndian.Uint64(b)
-}
-
-// RandInt64 returns a random int64 value. The returned value can be negative.
-func RandInt64() int64 {
-	return int64(RandUint64())
-}
diff --git a/pkg/snet/packet.go b/pkg/snet/packet.go
index 16cf7bda4..b95e65579 100644
--- a/pkg/snet/packet.go
+++ b/pkg/snet/packet.go
@@ -15,7 +15,7 @@
 package snet
 
 import (
-	"math/rand"
+	"math/rand/v2"
 
 	"github.com/gopacket/gopacket"
 
@@ -214,7 +214,7 @@ const (
 // WARNING: This is a transitional helper function, which will be removed
 // in the dispatcher-less snet; then, the underlay port must be used as identifier.
 func RandomSCMPIdentifer() uint16 {
-	id := SCMPIdentifierStart + rand.Int31n(SCMPIdentifierEnd-SCMPIdentifierStart+1)
+	id := SCMPIdentifierStart + rand.Int32N(SCMPIdentifierEnd-SCMPIdentifierStart+1)
 	return uint16(id)
 }
 
diff --git a/pkg/snet/squic/net.go b/pkg/snet/squic/net.go
index a0ceaff41..2055be958 100644
--- a/pkg/snet/squic/net.go
+++ b/pkg/snet/squic/net.go
@@ -19,7 +19,7 @@ import (
 	"crypto/tls"
 	"errors"
 	"fmt"
-	mrand "math/rand"
+	"math/rand/v2"
 	"net"
 	"sync"
 	"time"
@@ -398,7 +398,7 @@ func (d ConnDialer) Dial(ctx context.Context, dst net.Addr) (net.Conn, error) {
 			return nil, serrors.Wrap("dialing QUIC/SCION", err)
 		}
 
-		jitter := time.Duration(mrand.Int63n(int64(5 * time.Millisecond)))
+		jitter := time.Duration(rand.Int64N(int64(5 * time.Millisecond)))
 		select {
 		case <-time.After(sleep + jitter):
 		case <-ctx.Done():
diff --git a/private/app/path/path.go b/private/app/path/path.go
index 5a61b8bb6..456538df0 100644
--- a/private/app/path/path.go
+++ b/private/app/path/path.go
@@ -18,7 +18,7 @@ import (
 	"bufio"
 	"context"
 	"fmt"
-	"math/rand"
+	"math/rand/v2"
 	"net"
 	"os"
 	"sort"
@@ -118,7 +118,7 @@ func Choose(
 		return printAndChoose(paths, remote, o.colorScheme)
 	}
 
-	return paths[rand.Intn(len(paths))], nil
+	return paths[rand.IntN(len(paths))], nil
 }
 
 func filterUnhealthy(
diff --git a/private/segment/segfetcher/fetcher.go b/private/segment/segfetcher/fetcher.go
index 66ca01435..c367253a6 100644
--- a/private/segment/segfetcher/fetcher.go
+++ b/private/segment/segfetcher/fetcher.go
@@ -16,7 +16,7 @@ package segfetcher
 
 import (
 	"context"
-	"math/rand"
+	"math/rand/v2"
 	"net"
 	"time"
 
@@ -145,7 +145,7 @@ func (f *Fetcher) nextQuery(segs Segments) time.Time {
 // by the minimum and the configured query interval.
 func (f *Fetcher) nearestNextQueryTime(now, nextQuery time.Time) time.Time {
 	// Adding +-10% random jitter
-	jitterPercent := time.Duration(rand.Intn(20) - 10)
+	jitterPercent := time.Duration(rand.IntN(20) - 10)
 
 	if earliest := now.Add(minQueryInterval); nextQuery.Before(earliest) {
 		jitter := minQueryInterval * jitterPercent / 100
diff --git a/private/topology/interface.go b/private/topology/interface.go
index e194e46b3..a04d9b7ac 100644
--- a/private/topology/interface.go
+++ b/private/topology/interface.go
@@ -17,7 +17,7 @@ package topology
 import (
 	"crypto/sha256"
 	"encoding/json"
-	"math/rand"
+	"math/rand/v2"
 	"net"
 	"sort"
 
@@ -230,7 +230,7 @@ func (t *topologyS) Anycast(svc addr.SVC) (*net.UDPAddr, error) {
 	if err != nil {
 		return nil, err
 	}
-	return addrs[rand.Intn(len(addrs))], nil
+	return addrs[rand.IntN(len(addrs))], nil
 }
 
 func (t *topologyS) Multicast(svc addr.SVC) ([]*net.UDPAddr, error) {
diff --git a/private/topology/topology.go b/private/topology/topology.go
index 1d4a528db..01657fc54 100644
--- a/private/topology/topology.go
+++ b/private/topology/topology.go
@@ -18,7 +18,7 @@ package topology
 import (
 	"encoding/json"
 	"fmt"
-	"math/rand"
+	"math/rand/v2"
 	"net"
 	"net/netip"
 	"os"
@@ -666,7 +666,7 @@ func (s ServiceNames) GetRandom() (string, error) {
 	if numServers == 0 {
 		return "", serrors.New("No names present")
 	}
-	return s[rand.Intn(numServers)], nil
+	return s[rand.IntN(numServers)], nil
 }
 
 func copyUDPAddr(a *net.UDPAddr) *net.UDPAddr {
diff --git a/private/trust/db_inspector.go b/private/trust/db_inspector.go
index 0fa1665b1..23a918b25 100644
--- a/private/trust/db_inspector.go
+++ b/private/trust/db_inspector.go
@@ -17,7 +17,7 @@ package trust
 import (
 	"context"
 	"fmt"
-	"math/rand"
+	"math/rand/v2"
 	"time"
 
 	"github.com/patrickmn/go-cache"
@@ -197,5 +197,5 @@ func (i CachingInspector) cacheExpiration() time.Duration {
 	if dur == 0 {
 		dur = defaultCacheExpiration
 	}
-	return time.Duration(rand.Int63n(int64(dur-(dur/2))) + int64(dur/2))
+	return time.Duration(rand.Int64N(int64(dur-(dur/2))) + int64(dur/2))
 }
diff --git a/private/trust/verifier.go b/private/trust/verifier.go
index ef0a36659..edae024bc 100644
--- a/private/trust/verifier.go
+++ b/private/trust/verifier.go
@@ -18,7 +18,7 @@ import (
 	"context"
 	"crypto/x509"
 	"fmt"
-	"math/rand"
+	"math/rand/v2"
 	"net"
 	"time"
 
@@ -191,7 +191,7 @@ func (v *Verifier) cacheExpiration(chains [][]*x509.Certificate) time.Duration {
 	if dur == 0 {
 		dur = defaultCacheExpiration
 	}
-	validity := time.Duration(rand.Int63n(int64(dur-(dur/2))) + int64(dur/2))
+	validity := time.Duration(rand.Int64N(int64(dur-(dur/2))) + int64(dur/2))
 	expiration := time.Now().Add(validity)
 	for _, chain := range chains {
 		if notAfter := chain[0].NotAfter; notAfter.Before(expiration) {
diff --git a/router/bfd/jitter.go b/router/bfd/jitter.go
index ce046426d..ea2f0a74f 100644
--- a/router/bfd/jitter.go
+++ b/router/bfd/jitter.go
@@ -16,7 +16,7 @@ package bfd
 
 import (
 	"fmt"
-	"math/rand"
+	"math/rand/v2"
 	"time"
 )
 
@@ -96,7 +96,7 @@ func (g defaultIntervalGenerator) Generate(x, y int) int {
 
 func (g defaultIntervalGenerator) intn(n int) int {
 	if g.Source == nil {
-		return rand.Intn(n)
+		return rand.IntN(n)
 	}
 	return g.Source.Intn(n)
 }
diff --git a/router/svc.go b/router/svc.go
index ed1a656d5..c13ba5b6d 100644
--- a/router/svc.go
+++ b/router/svc.go
@@ -15,7 +15,7 @@
 package router
 
 import (
-	mrand "math/rand"
+	"math/rand/v2"
 	"net/netip"
 	"slices"
 	"sync"
@@ -65,5 +65,5 @@ func (s *services) Any(svc addr.SVC) (netip.AddrPort, bool) {
 	if len(addrs) == 0 {
 		return netip.AddrPort{}, false
 	}
-	return addrs[mrand.Intn(len(addrs))], true
+	return addrs[rand.IntN(len(addrs))], true
 }
diff --git a/tools/integration/integration.go b/tools/integration/integration.go
index b61d3e3e0..bf8daa218 100644
--- a/tools/integration/integration.go
+++ b/tools/integration/integration.go
@@ -21,7 +21,7 @@ import (
 	"flag"
 	"fmt"
 	"io"
-	"math/rand"
+	"math/rand/v2"
 	"net"
 	"os"
 	"path/filepath"
@@ -267,7 +267,7 @@ func loadAddr(ia addr.IA) *snet.UDPAddr {
 // interface kept similar to go 1.10
 func shuffle(n int, swap func(i, j int)) {
 	for i := n - 1; i > 0; i-- {
-		j := rand.Intn(i + 1)
+		j := rand.IntN(i + 1)
 		swap(i, j)
 	}
 }


Test patch:
diff --git a/control/beaconing/extender_test.go b/control/beaconing/extender_test.go
index 765712dbc..ceb93de7e 100644
--- a/control/beaconing/extender_test.go
+++ b/control/beaconing/extender_test.go
@@ -21,7 +21,7 @@ import (
 	"crypto/rand"
 	"errors"
 	"hash"
-	mrand "math/rand"
+	mrand "math/rand/v2"
 	"testing"
 	"time"
 
diff --git a/pkg/private/xtest/graph/graph.go b/pkg/private/xtest/graph/graph.go
index a80b87baa..985aac6f1 100644
--- a/pkg/private/xtest/graph/graph.go
+++ b/pkg/private/xtest/graph/graph.go
@@ -28,7 +28,7 @@ import (
 	"crypto/elliptic"
 	crand "crypto/rand"
 	"fmt"
-	"math/rand"
+	"math/rand/v2"
 	"sort"
 	"sync"
 	"time"
diff --git a/private/storage/path/dbtest/dbtest.go b/private/storage/path/dbtest/dbtest.go
index 920cbe499..38882c92c 100644
--- a/private/storage/path/dbtest/dbtest.go
+++ b/private/storage/path/dbtest/dbtest.go
@@ -18,7 +18,7 @@ import (
 	"bytes"
 	"context"
 	"fmt"
-	mrand "math/rand"
+	"math/rand/v2"
 	"sort"
 	"testing"
 	"time"
@@ -515,7 +515,7 @@ func AllocPathSegment(t *testing.T, ifs []uint64, infoTS uint32) (*seg.PathSegme
 		},
 	}
 
-	pseg, err := seg.CreateSegment(time.Unix(int64(infoTS), 0), uint16(mrand.Int()))
+	pseg, err := seg.CreateSegment(time.Unix(int64(infoTS), 0), uint16(rand.Int()))
 	require.NoError(t, err)
 	for _, ase := range ases {
 		signer := graph.NewSigner()
diff --git a/router/dataplane_internal_test.go b/router/dataplane_internal_test.go
index 738a2c52b..a560e18b2 100644
--- a/router/dataplane_internal_test.go
+++ b/router/dataplane_internal_test.go
@@ -16,9 +16,10 @@ package router
 
 import (
 	"bytes"
+	"crypto/rand"
 	"encoding/binary"
 	"hash/fnv"
-	"math/rand"
+	mrand "math/rand/v2"
 	"net"
 	"net/netip"
 	"reflect"
@@ -140,7 +141,7 @@ func TestForwarder(t *testing.T) {
 				for i, m := range ms {
 					totalCount++
 					// 1/5 of the packets (randomly chosen) are errors
-					if rand.Intn(5) == 0 {
+					if mrand.IntN(5) == 0 {
 						expectedPktId++
 						ms = ms[:i]
 						break