Return error from EncodePrefix instead of silently clamping bits

This commit is contained in:
Viktor Liu
2026-04-10 06:51:55 +02:00
parent 456298864c
commit 6c5ff88569
6 changed files with 72 additions and 41 deletions

View File

@@ -14,15 +14,14 @@ import (
)
// EncodePrefix encodes a netip.Prefix into compact bytes.
// The address is always unmapped before encoding. If unmapping produces a v4
// address, the prefix length is clamped to 32.
func EncodePrefix(p netip.Prefix) []byte {
// The address is always unmapped before encoding.
func EncodePrefix(p netip.Prefix) ([]byte, error) {
addr := p.Addr().Unmap()
bits := p.Bits()
if addr.Is4() && bits > 32 {
bits = 32
return nil, fmt.Errorf("invalid prefix length %d for IPv4 address %s (max 32)", bits, addr)
}
return append(addr.AsSlice(), byte(bits))
return append(addr.AsSlice(), byte(bits)), nil
}
// DecodePrefix decodes compact bytes into a netip.Prefix.
@@ -43,7 +42,7 @@ func DecodePrefix(b []byte) (netip.Prefix, error) {
bits := int(b[len(b)-1])
if addr.Is4() {
if bits > 32 {
bits = 32
return netip.Prefix{}, fmt.Errorf("invalid prefix length %d for v4-mapped address (max 32)", bits)
}
} else if bits > 128 {
return netip.Prefix{}, fmt.Errorf("invalid IPv6 prefix length %d (max 128)", bits)
@@ -62,7 +61,9 @@ func EncodeAddr(a netip.Addr) []byte {
if a.Is4() {
bits = 32
}
return EncodePrefix(netip.PrefixFrom(a, bits))
// Host prefix lengths are always valid for the address family, so error is impossible.
b, _ := EncodePrefix(netip.PrefixFrom(a, bits))
return b
}
// DecodeAddr decodes compact prefix bytes and returns only the address,

View File

@@ -59,7 +59,8 @@ func TestEncodeDecodePrefix(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
p := netip.MustParsePrefix(tt.prefix)
b := EncodePrefix(p)
b, err := EncodePrefix(p)
require.NoError(t, err)
assert.Equal(t, tt.size, len(b), "encoded size")
decoded, err := DecodePrefix(b)
@@ -72,7 +73,8 @@ func TestEncodeDecodePrefix(t *testing.T) {
func TestEncodePrefixUnmaps(t *testing.T) {
// v4-mapped v6 address should encode as v4
mapped := netip.MustParsePrefix("::ffff:10.1.2.3/32")
b := EncodePrefix(mapped)
b, err := EncodePrefix(mapped)
require.NoError(t, err)
assert.Equal(t, 5, len(b), "v4-mapped should encode as 5 bytes")
decoded, err := DecodePrefix(b)
@@ -80,24 +82,26 @@ func TestEncodePrefixUnmaps(t *testing.T) {
assert.Equal(t, netip.MustParsePrefix("10.1.2.3/32"), decoded)
}
func TestEncodePrefixUnmapsClampsBits(t *testing.T) {
// v4-mapped v6 with bits > 32 should clamp to /32
mapped := netip.MustParsePrefix("::ffff:10.1.2.3/128")
b := EncodePrefix(mapped)
func TestEncodePrefixUnmapsRejectsInvalidBits(t *testing.T) {
// v4-mapped v6 with bits > 32 should return an error
mapped128 := netip.MustParsePrefix("::ffff:10.1.2.3/128")
_, err := EncodePrefix(mapped128)
require.Error(t, err)
// v4-mapped v6 with bits=96 should also return an error
mapped96 := netip.MustParsePrefix("::ffff:10.0.0.0/96")
_, err = EncodePrefix(mapped96)
require.Error(t, err)
// v4-mapped v6 with bits=32 should succeed
mapped32 := netip.MustParsePrefix("::ffff:10.1.2.3/32")
b, err := EncodePrefix(mapped32)
require.NoError(t, err)
assert.Equal(t, 5, len(b), "v4-mapped should encode as 5 bytes")
decoded, err := DecodePrefix(b)
require.NoError(t, err)
assert.Equal(t, netip.MustParsePrefix("10.1.2.3/32"), decoded)
// v4-mapped v6 with bits=96 should also clamp to /32
mapped96 := netip.MustParsePrefix("::ffff:10.0.0.0/96")
b96 := EncodePrefix(mapped96)
assert.Equal(t, 5, len(b96))
decoded96, err := DecodePrefix(b96)
require.NoError(t, err)
assert.Equal(t, 32, decoded96.Bits())
}
func TestDecodeAddr(t *testing.T) {
@@ -147,16 +151,24 @@ func TestDecodePrefixInvalidBits(t *testing.T) {
}
func TestDecodePrefixUnmapsV6Input(t *testing.T) {
// If someone encodes a v4-mapped v6 as 17 bytes, decode should unmap it
// and clamp the prefix length to 32 for v4
addr := netip.MustParseAddr("::ffff:192.168.1.1")
// v4-mapped v6 with bits > 32 should return an error
raw := addr.As16()
b := make([]byte, 17)
copy(b, raw[:])
b[16] = 128
bInvalid := make([]byte, 17)
copy(bInvalid, raw[:])
bInvalid[16] = 128
decoded, err := DecodePrefix(b)
_, err := DecodePrefix(bInvalid)
require.Error(t, err, "v4-mapped address with /128 prefix should be rejected")
assert.Contains(t, err.Error(), "invalid prefix length")
// v4-mapped v6 with valid /32 should decode and unmap correctly
bValid := make([]byte, 17)
copy(bValid, raw[:])
bValid[16] = 32
decoded, err := DecodePrefix(bValid)
require.NoError(t, err)
assert.True(t, decoded.Addr().Is4(), "should be unmapped to v4")
assert.Equal(t, netip.MustParsePrefix("192.168.1.1/32"), decoded)