Skip to content

Commit

Permalink
lxd/network: Use shared.ParseIPRanges
Browse files Browse the repository at this point in the history
See canonical/microcloud#210

Signed-off-by: Wesley Hershberger <wesley.hershberger@canonical.com>
  • Loading branch information
MggMuggins committed Mar 25, 2024
1 parent f1ae0fe commit 3c188e5
Show file tree
Hide file tree
Showing 4 changed files with 8 additions and 270 deletions.
12 changes: 6 additions & 6 deletions lxd/network/driver_bridge.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
Expand All @@ -850,22 +850,22 @@ 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)
}

// 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)
}
}
Expand Down
4 changes: 2 additions & 2 deletions lxd/network/driver_ovn.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -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)
}
Expand Down
123 changes: 0 additions & 123 deletions lxd/network/network_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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)
Expand Down
139 changes: 0 additions & 139 deletions lxd/network/network_utils_test.go

This file was deleted.

0 comments on commit 3c188e5

Please sign in to comment.