From 3c188e55f4dc5c961ba569a826a8dc99cebdf4e5 Mon Sep 17 00:00:00 2001 From: Wesley Hershberger Date: Mon, 25 Mar 2024 17:34:59 -0500 Subject: [PATCH] lxd/network: Use `shared.ParseIPRanges` See canonical/microcloud#210 Signed-off-by: Wesley Hershberger --- lxd/network/driver_bridge.go | 12 +-- lxd/network/driver_ovn.go | 4 +- lxd/network/network_utils.go | 123 -------------------------- lxd/network/network_utils_test.go | 139 ------------------------------ 4 files changed, 8 insertions(+), 270 deletions(-) delete mode 100644 lxd/network/network_utils_test.go diff --git a/lxd/network/driver_bridge.go b/lxd/network/driver_bridge.go index 2dfc3a055ebf..80b1235847c5 100644 --- a/lxd/network/driver_bridge.go +++ b/lxd/network/driver_bridge.go @@ -818,19 +818,19 @@ func (n *bridge) Validate(config map[string]string) error { allowedNets = append(allowedNets, dhcpSubnet) } - ovnRanges, err := parseIPRanges(config["ipv4.ovn.ranges"], allowedNets...) + ovnRanges, err := shared.ParseIPRanges(config["ipv4.ovn.ranges"], allowedNets...) if err != nil { return fmt.Errorf("Failed parsing ipv4.ovn.ranges: %w", err) } - dhcpRanges, err := parseIPRanges(config["ipv4.dhcp.ranges"], allowedNets...) + dhcpRanges, err := shared.ParseIPRanges(config["ipv4.dhcp.ranges"], allowedNets...) if err != nil { return fmt.Errorf("Failed parsing ipv4.dhcp.ranges: %w", err) } for _, ovnRange := range ovnRanges { for _, dhcpRange := range dhcpRanges { - if IPRangesOverlap(ovnRange, dhcpRange) { + if ovnRange.Overlaps(dhcpRange) { return fmt.Errorf(`The range specified in "ipv4.ovn.ranges" (%q) cannot overlap with "ipv4.dhcp.ranges"`, ovnRange) } } @@ -850,7 +850,7 @@ func (n *bridge) Validate(config map[string]string) error { allowedNets = append(allowedNets, dhcpSubnet) } - ovnRanges, err := parseIPRanges(config["ipv6.ovn.ranges"], allowedNets...) + ovnRanges, err := shared.ParseIPRanges(config["ipv6.ovn.ranges"], allowedNets...) if err != nil { return fmt.Errorf("Failed parsing ipv6.ovn.ranges: %w", err) } @@ -858,14 +858,14 @@ func (n *bridge) Validate(config map[string]string) error { // If stateful DHCPv6 is enabled, check OVN ranges don't overlap with DHCPv6 stateful ranges. // Otherwise SLAAC will be being used to generate client IPs and predefined ranges aren't used. if dhcpSubnet != nil && shared.IsTrue(config["ipv6.dhcp.stateful"]) { - dhcpRanges, err := parseIPRanges(config["ipv6.dhcp.ranges"], allowedNets...) + dhcpRanges, err := shared.ParseIPRanges(config["ipv6.dhcp.ranges"], allowedNets...) if err != nil { return fmt.Errorf("Failed parsing ipv6.dhcp.ranges: %w", err) } for _, ovnRange := range ovnRanges { for _, dhcpRange := range dhcpRanges { - if IPRangesOverlap(ovnRange, dhcpRange) { + if ovnRange.Overlaps(dhcpRange) { return fmt.Errorf(`The range specified in "ipv6.ovn.ranges" (%q) cannot overlap with "ipv6.dhcp.ranges"`, ovnRange) } } diff --git a/lxd/network/driver_ovn.go b/lxd/network/driver_ovn.go index f16dfa724be3..051ae244c9da 100644 --- a/lxd/network/driver_ovn.go +++ b/lxd/network/driver_ovn.go @@ -1159,7 +1159,7 @@ func (n *ovn) allocateUplinkPortIPs(uplinkNet Network, routerMAC net.HardwareAdd return fmt.Errorf(`Missing required "ipv4.ovn.ranges" config key on uplink network`) } - ipRanges, err := parseIPRanges(uplinkNetConf["ipv4.ovn.ranges"], uplinkNet.DHCPv4Subnet()) + ipRanges, err := shared.ParseIPRanges(uplinkNetConf["ipv4.ovn.ranges"], uplinkNet.DHCPv4Subnet()) if err != nil { return fmt.Errorf("Failed to parse uplink IPv4 OVN ranges: %w", err) } @@ -1175,7 +1175,7 @@ func (n *ovn) allocateUplinkPortIPs(uplinkNet Network, routerMAC net.HardwareAdd if uplinkIPv6Net != nil && routerExtPortIPv6 == nil { // If IPv6 OVN ranges are specified by the uplink, allocate from them. if uplinkNetConf["ipv6.ovn.ranges"] != "" { - ipRanges, err := parseIPRanges(uplinkNetConf["ipv6.ovn.ranges"], uplinkNet.DHCPv6Subnet()) + ipRanges, err := shared.ParseIPRanges(uplinkNetConf["ipv6.ovn.ranges"], uplinkNet.DHCPv6Subnet()) if err != nil { return fmt.Errorf("Failed to parse uplink IPv6 OVN ranges: %w", err) } diff --git a/lxd/network/network_utils.go b/lxd/network/network_utils.go index d1006527891a..3a0a3cb126d2 100644 --- a/lxd/network/network_utils.go +++ b/lxd/network/network_utils.go @@ -969,116 +969,6 @@ func randomHwaddr(r *rand.Rand) string { return ret.String() } -// parseIPRange parses an IP range in the format "start-end" and converts it to a shared.IPRange. -// If allowedNets are supplied, then each IP in the range is checked that it belongs to at least one of them. -// IPs in the range can be zero prefixed, e.g. "::1" or "0.0.0.1", however they should not overlap with any -// supplied allowedNets prefixes. If they are within an allowed network, any zero prefixed addresses are -// returned combined with the first allowed network they are within. -// If no allowedNets supplied they are returned as-is. -func parseIPRange(ipRange string, allowedNets ...*net.IPNet) (*shared.IPRange, error) { - inAllowedNet := func(ip net.IP, allowedNet *net.IPNet) net.IP { - if ip == nil { - return nil - } - - ipv4 := ip.To4() - - // Only match IPv6 addresses against IPv6 networks. - if ipv4 == nil && allowedNet.IP.To4() != nil { - return nil - } - - // Combine IP with network prefix if IP starts with a zero. - // If IP is v4, then compare against 4-byte representation, otherwise use 16 byte representation. - if (ipv4 != nil && ipv4[0] == 0) || (ipv4 == nil && ip[0] == 0) { - allowedNet16 := allowedNet.IP.To16() - ipCombined := make(net.IP, net.IPv6len) - for i, b := range ip { - ipCombined[i] = allowedNet16[i] | b - } - - ip = ipCombined - } - - // Check start IP is within one of the allowed networks. - if !allowedNet.Contains(ip) { - return nil - } - - return ip - } - - rangeParts := strings.SplitN(ipRange, "-", 2) - if len(rangeParts) != 2 { - return nil, fmt.Errorf("IP range %q must contain start and end IP addresses", ipRange) - } - - startIP := net.ParseIP(rangeParts[0]) - endIP := net.ParseIP(rangeParts[1]) - - if startIP == nil { - return nil, fmt.Errorf("Start IP %q is invalid", rangeParts[0]) - } - - if endIP == nil { - return nil, fmt.Errorf("End IP %q is invalid", rangeParts[1]) - } - - if bytes.Compare(startIP, endIP) > 0 { - return nil, fmt.Errorf("Start IP %q must be less than End IP %q", startIP, endIP) - } - - if len(allowedNets) > 0 { - matchFound := false - for _, allowedNet := range allowedNets { - if allowedNet == nil { - return nil, fmt.Errorf("Invalid allowed network") - } - - combinedStartIP := inAllowedNet(startIP, allowedNet) - if combinedStartIP == nil { - continue - } - - combinedEndIP := inAllowedNet(endIP, allowedNet) - if combinedEndIP == nil { - continue - } - - // If both match then replace parsed IPs with combined IPs and stop searching. - matchFound = true - startIP = combinedStartIP - endIP = combinedEndIP - break - } - - if !matchFound { - return nil, fmt.Errorf("IP range %q does not fall within any of the allowed networks %v", ipRange, allowedNets) - } - } - - return &shared.IPRange{ - Start: startIP, - End: endIP, - }, nil -} - -// parseIPRanges parses a comma separated list of IP ranges using parseIPRange. -func parseIPRanges(ipRangesList string, allowedNets ...*net.IPNet) ([]*shared.IPRange, error) { - ipRanges := strings.Split(ipRangesList, ",") - netIPRanges := make([]*shared.IPRange, 0, len(ipRanges)) - for _, ipRange := range ipRanges { - netIPRange, err := parseIPRange(strings.TrimSpace(ipRange), allowedNets...) - if err != nil { - return nil, err - } - - netIPRanges = append(netIPRanges, netIPRange) - } - - return netIPRanges, nil -} - // VLANInterfaceCreate creates a VLAN interface on parent interface (if needed). // Returns boolean indicating if VLAN interface was created. func VLANInterfaceCreate(parent string, vlanDevice string, vlanID string, gvrp bool) (bool, error) { @@ -1241,19 +1131,6 @@ func SubnetParseAppend(subnets []*net.IPNet, parseSubnet ...string) ([]*net.IPNe return subnets, nil } -// IPRangesOverlap checks whether two ip ranges have ip addresses in common. -func IPRangesOverlap(r1, r2 *shared.IPRange) bool { - if r1.End == nil { - return r2.ContainsIP(r1.Start) - } - - if r2.End == nil { - return r1.ContainsIP(r2.Start) - } - - return r1.ContainsIP(r2.Start) || r1.ContainsIP(r2.End) -} - // InterfaceStatus returns the global unicast IP addresses configured on an interface and whether it is up or not. func InterfaceStatus(nicName string) ([]net.IP, bool, error) { iface, err := net.InterfaceByName(nicName) diff --git a/lxd/network/network_utils_test.go b/lxd/network/network_utils_test.go deleted file mode 100644 index a5eb789f35e3..000000000000 --- a/lxd/network/network_utils_test.go +++ /dev/null @@ -1,139 +0,0 @@ -package network - -import ( - "fmt" - "net" - - "github.com/canonical/lxd/shared" -) - -func Example_parseIPRange() { - _, allowedv4NetworkA, _ := net.ParseCIDR("192.168.1.0/24") - _, allowedv4NetworkB, _ := net.ParseCIDR("192.168.0.0/16") - _, allowedv6NetworkA, _ := net.ParseCIDR("fd22:c952:653e:3df6::/64") - _, allowedv6NetworkB, _ := net.ParseCIDR("fd22:c952:653e::/48") - - ipRanges := []string{ - // Ranges within allowedv4NetworkA. - "192.168.1.1-192.168.1.255", - "0.0.0.1-192.168.1.255", - "0.0.0.1-0.0.0.255", - // Ranges outsde of allowedv4NetworkA but within allowedv4NetworkB. - "192.168.0.1-192.168.0.255", - "192.168.0.0-192.168.0.0", - "0.0.2.0-0.0.2.255", - // Invalid IP ranges. - "0.0.0.0.1-192.168.1.255", - "192.0.0.1-192.0.0.255", - "0.0.0.1-1.0.0.255", - "0.0.2.1-0.0.0.255", - // Ranges within allowedv6NetworkA. - "fd22:c952:653e:3df6::1-fd22:c952:653e:3df6::FFFF", - "::1-::FFFF", - // Ranges outsde of allowedv6NetworkA but within allowedv6NetworkB. - "fd22:c952:653e:FFFF::1-fd22:c952:653e:FFFF::FFFF", - "::AAAA:FFFF:FFFF:FFFF:1-::AAAA:FFFF:FFFF:FFFF:FFFF", - } - - fmt.Println("With allowed networks") - for _, ipRange := range ipRanges { - parsedRange, err := parseIPRange(ipRange, allowedv4NetworkA, allowedv4NetworkB, allowedv6NetworkA, allowedv6NetworkB) - if err != nil { - fmt.Printf("Err: %v\n", err) - continue - } - - fmt.Printf("Start: %s, End: %s\n", parsedRange.Start.String(), parsedRange.End.String()) - } - - fmt.Println("Without allowed networks") - for _, ipRange := range ipRanges { - parsedRange, err := parseIPRange(ipRange) - if err != nil { - fmt.Printf("Err: %v\n", err) - continue - } - - fmt.Printf("Start: %s, End: %s\n", parsedRange.Start.String(), parsedRange.End.String()) - } - - // Output: With allowed networks - // Start: 192.168.1.1, End: 192.168.1.255 - // Start: 192.168.1.1, End: 192.168.1.255 - // Start: 192.168.1.1, End: 192.168.1.255 - // Start: 192.168.0.1, End: 192.168.0.255 - // Start: 192.168.0.0, End: 192.168.0.0 - // Start: 192.168.2.0, End: 192.168.2.255 - // Err: Start IP "0.0.0.0.1" is invalid - // Err: IP range "192.0.0.1-192.0.0.255" does not fall within any of the allowed networks [192.168.1.0/24 192.168.0.0/16 fd22:c952:653e:3df6::/64 fd22:c952:653e::/48] - // Err: IP range "0.0.0.1-1.0.0.255" does not fall within any of the allowed networks [192.168.1.0/24 192.168.0.0/16 fd22:c952:653e:3df6::/64 fd22:c952:653e::/48] - // Err: Start IP "0.0.2.1" must be less than End IP "0.0.0.255" - // Start: fd22:c952:653e:3df6::1, End: fd22:c952:653e:3df6::ffff - // Start: fd22:c952:653e:3df6::1, End: fd22:c952:653e:3df6::ffff - // Start: fd22:c952:653e:ffff::1, End: fd22:c952:653e:ffff::ffff - // Start: fd22:c952:653e:aaaa:ffff:ffff:ffff:1, End: fd22:c952:653e:aaaa:ffff:ffff:ffff:ffff - // Without allowed networks - // Start: 192.168.1.1, End: 192.168.1.255 - // Start: 0.0.0.1, End: 192.168.1.255 - // Start: 0.0.0.1, End: 0.0.0.255 - // Start: 192.168.0.1, End: 192.168.0.255 - // Start: 192.168.0.0, End: 192.168.0.0 - // Start: 0.0.2.0, End: 0.0.2.255 - // Err: Start IP "0.0.0.0.1" is invalid - // Start: 192.0.0.1, End: 192.0.0.255 - // Start: 0.0.0.1, End: 1.0.0.255 - // Err: Start IP "0.0.2.1" must be less than End IP "0.0.0.255" - // Start: fd22:c952:653e:3df6::1, End: fd22:c952:653e:3df6::ffff - // Start: ::1, End: ::ffff - // Start: fd22:c952:653e:ffff::1, End: fd22:c952:653e:ffff::ffff - // Start: ::aaaa:ffff:ffff:ffff:1, End: ::aaaa:ffff:ffff:ffff:ffff -} - -func Example_ipRangesOverlap() { - rangePairs := [][2]string{ - {"10.1.1.1-10.1.1.2", "10.1.1.3-10.1.1.4"}, - {"10.1.1.1-10.1.2.1", "10.1.1.254-10.1.1.255"}, - {"10.1.1.1-10.1.1.6", "10.1.1.5-10.1.1.9"}, - {"10.1.1.5-10.1.1.9", "10.1.1.1-10.1.1.6"}, - {"::1-::2", "::3-::4"}, - {"::1-::6", "::5-::9"}, - {"::5-::9", "::1-::6"}, - } - - for _, pair := range rangePairs { - r0, _ := parseIPRange(pair[0]) - r1, _ := parseIPRange(pair[1]) - result := IPRangesOverlap(r0, r1) - fmt.Printf("Range1: %v, Range2: %v, overlapped: %t\n", r0, r1, result) - } - - // also do a couple of tests with ranges that have no end - singleIPRange := &shared.IPRange{ - Start: net.ParseIP("10.1.1.4"), - } - - otherRange, _ := parseIPRange("10.1.1.1-10.1.1.6") - - fmt.Printf("Range1: %v, Range2: %v, overlapped: %t\n", singleIPRange, otherRange, IPRangesOverlap(singleIPRange, otherRange)) - fmt.Printf("Range1: %v, Range2: %v, overlapped: %t\n", otherRange, singleIPRange, IPRangesOverlap(otherRange, singleIPRange)) - fmt.Printf("Range1: %v, Range2: %v, overlapped: %t\n", singleIPRange, singleIPRange, IPRangesOverlap(singleIPRange, singleIPRange)) - - otherRange, _ = parseIPRange("10.1.1.8-10.1.1.9") - - fmt.Printf("Range1: %v, Range2: %v, overlapped: %t\n", singleIPRange, otherRange, IPRangesOverlap(singleIPRange, otherRange)) - fmt.Printf("Range1: %v, Range2: %v, overlapped: %t\n", otherRange, singleIPRange, IPRangesOverlap(otherRange, singleIPRange)) - - // Output: - // Range1: 10.1.1.1-10.1.1.2, Range2: 10.1.1.3-10.1.1.4, overlapped: false - // Range1: 10.1.1.1-10.1.2.1, Range2: 10.1.1.254-10.1.1.255, overlapped: true - // Range1: 10.1.1.1-10.1.1.6, Range2: 10.1.1.5-10.1.1.9, overlapped: true - // Range1: 10.1.1.5-10.1.1.9, Range2: 10.1.1.1-10.1.1.6, overlapped: true - // Range1: ::1-::2, Range2: ::3-::4, overlapped: false - // Range1: ::1-::6, Range2: ::5-::9, overlapped: true - // Range1: ::5-::9, Range2: ::1-::6, overlapped: true - // Range1: 10.1.1.4, Range2: 10.1.1.1-10.1.1.6, overlapped: true - // Range1: 10.1.1.1-10.1.1.6, Range2: 10.1.1.4, overlapped: true - // Range1: 10.1.1.4, Range2: 10.1.1.4, overlapped: true - // Range1: 10.1.1.4, Range2: 10.1.1.8-10.1.1.9, overlapped: false - // Range1: 10.1.1.8-10.1.1.9, Range2: 10.1.1.4, overlapped: false -}