db: handle degenerate prefixes in random IP allocation

Single-address prefixes panicked; zero-high-byte addresses failed to encode.
This commit is contained in:
Kristoffer Dalby
2026-06-05 15:12:05 +00:00
committed by Kristoffer Dalby
parent 10696fa634
commit 99ad555d64
2 changed files with 45 additions and 1 deletions
+10 -1
View File
@@ -251,6 +251,12 @@ func randomNext(pfx netip.Prefix) (netip.Addr, error) {
// after.
tempMax := big.NewInt(0).Sub(&to, &from)
// A single-address prefix (/32 or /128) has from == to, so tempMax is 0 and
// rand.Int would panic on a non-positive bound. Return the sole address.
if tempMax.Sign() <= 0 {
return fromIP, nil
}
out, err := rand.Int(rand.Reader, tempMax)
if err != nil {
return netip.Addr{}, fmt.Errorf("generating random IP: %w", err)
@@ -258,7 +264,10 @@ func randomNext(pfx netip.Prefix) (netip.Addr, error) {
valInRange := big.NewInt(0).Add(&from, out)
ip, ok := netip.AddrFromSlice(valInRange.Bytes())
// big.Int.Bytes() strips leading zero bytes, so a value with a zero high
// byte yields a too-short slice that AddrFromSlice rejects. Pad to the
// prefix's address width.
ip, ok := netip.AddrFromSlice(valInRange.FillBytes(make([]byte, len(fromIP.AsSlice()))))
if !ok {
return netip.Addr{}, errGeneratedIPBytesInvalid
}
+35
View File
@@ -0,0 +1,35 @@
package db
import (
"net/netip"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// TestRandomNextSingleAddressPrefix ensures a single-address prefix (/32 or
// /128) does not panic. from == to makes the random range zero, and rand.Int
// panics on a non-positive bound; the sole address must be returned instead.
func TestRandomNextSingleAddressPrefix(t *testing.T) {
for _, p := range []string{"100.64.0.1/32", "fd7a:115c:a1e0::1/128"} {
pfx := netip.MustParsePrefix(p)
require.NotPanics(t, func() {
ip, err := randomNext(pfx)
require.NoError(t, err)
assert.Equal(t, pfx.Addr(), ip)
}, "prefix %s", p)
}
}
// TestRandomNextLeadingZeroBytes ensures prefixes whose addresses have a zero
// high byte allocate successfully. big.Int.Bytes() strips leading zeros, so the
// drawn value would be too short for netip.AddrFromSlice without padding.
func TestRandomNextLeadingZeroBytes(t *testing.T) {
pfx := netip.MustParsePrefix("0.0.0.0/16")
for range 100 {
ip, err := randomNext(pfx)
require.NoError(t, err)
assert.True(t, pfx.Contains(ip), "ip %s not in %s", ip, pfx)
}
}