From e29384d040c5576d9fb572c6ee81a231f67af7db Mon Sep 17 00:00:00 2001 From: Andreas Karis Date: Wed, 19 Jul 2023 12:42:00 +0200 Subject: [PATCH] IterateForAssignment: Properly handle invalid syntax for exclude range Correctly handle errors from parsing exclude ranges to avoid issues with nil pointer exceptions. Parse single IP addresses such as 192.168.123.10 or fe02::10 and convert them to CIDRs by appending the correct prefix. Signed-off-by: Andreas Karis --- pkg/allocate/allocate.go | 31 +++++++++++++++++++- pkg/allocate/allocate_test.go | 53 +++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 1 deletion(-) diff --git a/pkg/allocate/allocate.go b/pkg/allocate/allocate.go index 607592d16..009c8544f 100644 --- a/pkg/allocate/allocate.go +++ b/pkg/allocate/allocate.go @@ -102,10 +102,14 @@ func IterateForAssignment(ipnet net.IPNet, rangeStart net.IP, rangeEnd net.IP, r for _, r := range reserveList { reserved[r.IP.String()] = true } + // Build excluded list, "192.168.2.229/30", "192.168.1.229/30". excluded := []*net.IPNet{} for _, v := range excludeRanges { - _, subnet, _ := net.ParseCIDR(v) + subnet, err := parseExcludedRange(v) + if err != nil { + return net.IP{}, reserveList, fmt.Errorf("could not parse exclude range, err: %q", err) + } excluded = append(excluded, subnet) } @@ -144,3 +148,28 @@ func skipExcludedSubnets(ip net.IP, excluded []*net.IPNet) net.IP { } return nil } + +// parseExcludedRange parses a provided string to a net.IPNet. +// If the provided string is a valid CIDR, return the net.IPNet for that CIDR. +// If the provided string is a valid IP address, add the /32 or /128 prefix to form the CIDR and return the net.IPNet. +// Otherwise, return the error. +func parseExcludedRange(s string) (*net.IPNet, error) { + // Try parsing CIDRs. + _, subnet, err := net.ParseCIDR(s) + if err == nil { + return subnet, nil + } + // The user might have given a single IP address, try parsing that - if it does not parse, return the error that + // we got earlier. + ip := net.ParseIP(s) + if ip == nil { + return nil, err + } + // If the address parses, check if it's IPv4 or IPv6 and add the correct prefix. + if ip.To4() != nil { + _, subnet, err = net.ParseCIDR(fmt.Sprintf("%s/32", s)) + } else { + _, subnet, err = net.ParseCIDR(fmt.Sprintf("%s/128", s)) + } + return subnet, err +} diff --git a/pkg/allocate/allocate_test.go b/pkg/allocate/allocate_test.go index 0940a7da6..d88ed9696 100644 --- a/pkg/allocate/allocate_test.go +++ b/pkg/allocate/allocate_test.go @@ -113,6 +113,33 @@ var _ = Describe("Allocation operations", func() { }) + It("can IterateForAssignment on an IPv4 address excluding a range which is a single IP", func() { + firstip, ipnet, err := net.ParseCIDR("192.168.0.0/29") + Expect(err).NotTo(HaveOccurred()) + + // figure out the range start. + calculatedrangestart := net.ParseIP(firstip.Mask(ipnet.Mask).String()) + + var ipres []types.IPReservation + exrange := []string{"192.168.0.1"} + newip, _, err := IterateForAssignment(*ipnet, calculatedrangestart, nil, ipres, exrange, "0xdeadbeef", "") + Expect(err).NotTo(HaveOccurred()) + Expect(fmt.Sprint(newip)).To(Equal("192.168.0.2")) + }) + + It("correctly handles invalid syntax for an exclude range with IPv4", func() { + firstip, ipnet, err := net.ParseCIDR("192.168.0.0/29") + Expect(err).NotTo(HaveOccurred()) + + // figure out the range start. + calculatedrangestart := net.ParseIP(firstip.Mask(ipnet.Mask).String()) + + var ipres []types.IPReservation + exrange := []string{"192.168.0.1/123"} + _, _, err = IterateForAssignment(*ipnet, calculatedrangestart, nil, ipres, exrange, "0xdeadbeef", "") + Expect(err).To(MatchError(HavePrefix("could not parse exclude range"))) + }) + It("can IterateForAssignment on an IPv6 address excluding a range", func() { firstip, ipnet, err := net.ParseCIDR("100::2:1/125") @@ -128,6 +155,32 @@ var _ = Describe("Allocation operations", func() { }) + It("can IterateForAssignment on an IPv6 address excluding a range which is a single IP", func() { + firstip, ipnet, err := net.ParseCIDR("100::2:1/125") + Expect(err).NotTo(HaveOccurred()) + + // figure out the range start. + calculatedrangestart := net.ParseIP(firstip.Mask(ipnet.Mask).String()) + + var ipres []types.IPReservation + exrange := []string{"100::2:1"} + newip, _, _ := IterateForAssignment(*ipnet, calculatedrangestart, nil, ipres, exrange, "0xdeadbeef", "") + Expect(fmt.Sprint(newip)).To(Equal("100::2:2")) + }) + + It("correctly handles invalid syntax for an exclude range with IPv6", func() { + firstip, ipnet, err := net.ParseCIDR("100::2:1/125") + Expect(err).NotTo(HaveOccurred()) + + // figure out the range start. + calculatedrangestart := net.ParseIP(firstip.Mask(ipnet.Mask).String()) + + var ipres []types.IPReservation + exrange := []string{"100::2::1"} + _, _, err = IterateForAssignment(*ipnet, calculatedrangestart, nil, ipres, exrange, "0xdeadbeef", "") + Expect(err).To(MatchError(HavePrefix("could not parse exclude range"))) + }) + It("can IterateForAssignment on an IPv6 address excluding a very large range", func() { firstip, ipnet, err := net.ParseCIDR("2001:db8::/30")