From 0785f379a2770c8e7632f347131973cc0f55b094 Mon Sep 17 00:00:00 2001 From: pascal Date: Wed, 29 Jul 2026 12:05:30 +0200 Subject: [PATCH] input validation for peer ip allocation --- management/server/types/network.go | 19 +++++++++++++++++ management/server/types/network_test.go | 28 +++++++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/management/server/types/network.go b/management/server/types/network.go index 024a4a0fe..9c22be974 100644 --- a/management/server/types/network.go +++ b/management/server/types/network.go @@ -191,10 +191,22 @@ func (n *Network) Copy() *Network { } } +// validateIPv4Prefix ensures the prefix is an IPv4 network with assignable host addresses. +func validateIPv4Prefix(prefix netip.Prefix) error { + if !prefix.IsValid() || !prefix.Addr().Is4() || prefix.Bits() < 1 || prefix.Bits() >= 31 { + return fmt.Errorf("invalid IPv4 subnet: %s", prefix.String()) + } + return nil +} + // AllocatePeerIP picks an available IP from a netip.Prefix. // This method considers already taken IPs and reuses IPs if there are gaps in takenIps. // E.g. if prefix=100.30.0.0/16 and takenIps=[100.30.0.1, 100.30.0.4] then the result would be 100.30.0.2 or 100.30.0.3. func AllocatePeerIP(prefix netip.Prefix, takenIps []netip.Addr) (netip.Addr, error) { + if err := validateIPv4Prefix(prefix); err != nil { + return netip.Addr{}, err + } + b := prefix.Masked().Addr().As4() baseIP := binary.BigEndian.Uint32(b[:]) hostBits := 32 - prefix.Bits() @@ -205,6 +217,9 @@ func AllocatePeerIP(prefix netip.Prefix, takenIps []netip.Addr) (netip.Addr, err taken[baseIP+totalIPs-1] = struct{}{} // reserve broadcast IP for _, ip := range takenIps { + if !ip.Is4() { + continue + } ab := ip.As4() taken[binary.BigEndian.Uint32(ab[:])] = struct{}{} } @@ -231,6 +246,10 @@ func AllocatePeerIP(prefix netip.Prefix, takenIps []netip.Addr) (netip.Addr, err // AllocateRandomPeerIP picks a random available IP from a netip.Prefix. func AllocateRandomPeerIP(prefix netip.Prefix) (netip.Addr, error) { + if err := validateIPv4Prefix(prefix); err != nil { + return netip.Addr{}, err + } + b := prefix.Masked().Addr().As4() baseIP := binary.BigEndian.Uint32(b[:]) hostBits := 32 - prefix.Bits() diff --git a/management/server/types/network_test.go b/management/server/types/network_test.go index d8a06dbbc..239f72426 100644 --- a/management/server/types/network_test.go +++ b/management/server/types/network_test.go @@ -143,6 +143,34 @@ func TestAllocatePeerIPVariousCIDRs(t *testing.T) { } } +func TestAllocateIPv4InvalidPrefixes(t *testing.T) { + prefixes := []netip.Prefix{ + {}, + netip.MustParsePrefix("0.0.0.0/0"), + netip.MustParsePrefix("192.168.1.0/31"), + netip.MustParsePrefix("192.168.1.1/32"), + netip.MustParsePrefix("fd12:3456:7890:abcd::/64"), + } + + for _, prefix := range prefixes { + t.Run(prefix.String(), func(t *testing.T) { + _, err := AllocatePeerIP(prefix, nil) + assert.Error(t, err) + + _, err = AllocateRandomPeerIP(prefix) + assert.Error(t, err) + }) + } +} + +func TestAllocatePeerIPIgnoresNonIPv4TakenIPs(t *testing.T) { + prefix := netip.MustParsePrefix("192.168.1.0/29") + + ip, err := AllocatePeerIP(prefix, []netip.Addr{netip.MustParseAddr("fd12:3456:7890:abcd::1")}) + require.NoError(t, err) + assert.True(t, prefix.Contains(ip)) +} + func TestGenerateIPs(t *testing.T) { ipNet := net.IPNet{IP: net.ParseIP("100.64.0.0"), Mask: net.IPMask{255, 255, 255, 0}} ips, ipsLen := generateIPs(&ipNet, map[string]struct{}{"100.64.0.0": {}})